13

I am struggling to figure out an issue with manipulating JSON with Aeson lenses. My task is as simple as to add a key to a nested object in JSON. I was able to change the existing keyby means of:

> :set -XOverloadedStrings
> import Control.Lens
> import Data.Aeson
> import Data.Aeson.Lens
> "{ \"a\": { \"b\": 10 } }" & key "a" . key "b" .~ String "jee"
"{\"a\":{\"b\":\"jee\"}}"

But when I try to make it deal with the new key, it just silently fails to add it:

> "{ \"a\": { \"b\": 10 } }" & key "a" . key "c" .~ String "jee"   
"{\"a\":{\"b\":10}}"

Certainly it's me doing something wrong, but I figure I'm out of mana to understand what exactly.

Would you kindly point me in the right direction?

Thank you!

SkyWriter
  • 1,454
  • 10
  • 17

2 Answers2

20

As dfeuer noted, at can insert into maps, while key and ix merely traverse elements if they exist. We can do the following:

> "{ \"a\": { \"b\": 10 } }" & key "a" . _Object . at "c" ?~ String "foo"
"{\"a\":{\"b\":10,\"c\":\"foo\"}}

at is a lens focusing on Maybe element-s, and we can insert by setting to Just some element, and remove by setting to Nothing. at "c" ?~ String "foo" is the same as at "c" .~ Just (String "foo").

If we want to do nested inserts, we can use non to define a default value to be inserted:

> "{ \"a\": { \"b\": 10 } }" & key "a" . _Object . at "c" . non (Object mempty) . _Object . at "d" ?~ String "foo"
"{\"a\":{\"b\":10,\"c\":{\"d\":\"foo\"}}}"

This is a mouthful, so we can factor some parts out:

> let atKey k = _Object . at k
> "{ \"a\": { \"b\": 10 } }" & key "a" . atKey "c" . non (Object mempty) . atKey "d" ?~ String "foo"
András Kovács
  • 29,931
  • 3
  • 53
  • 99
3

key is based on ix, whose documentation indicates it isn't powerful enough to do what you want and points to Control.Lens.At.at. I'm pretty sure that should do the trick for you. The basic idea is that you start with the _Object prism to turn the JSON text into an object, then use at key to get a lens into that field as a Maybe. You can then change it to Just what you want.

This will work very well as long as all the objects along the path you wish to take exist. If you want to (potentially) start from nothing and create a chain of single-field objects, you will likely find things more annoying. Fortunately, you probably don't need to do this.

dfeuer
  • 48,079
  • 5
  • 63
  • 167
  • Thanks for pointing out the reasons why `key` doesn't work for my case. I really tried to look into the source code, but it makes even less sense :-) Guess it takes time to get head around this. – SkyWriter Dec 29 '15 at 17:06
  • @SkyWriter, if you're familiar with model/view terminology, you can think of `_Object` as providing a complete `Object` "view" of a `Text` model, but only if that `Text` successfully parses as an `Object`. Assuming the view succeeds, you can both inspect and modify the `Text` model through that view. – dfeuer Dec 29 '15 at 19:44