Using lens to add key and value to a nested Map
Asked Answered
R

2

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!

Richma answered 29/12, 2015 at 12:33 Comment(0)
F
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"
Farfetched answered 29/12, 2015 at 16:40 Comment(2)
Thank you for providing a working example for my case and additional explanation!Richma
It would make sense for (a version of) .~/set to return a Maybe for situations like this, where sets can be meaningless at runtime.Contemptible
R
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.

Rensselaerite answered 29/12, 2015 at 15:29 Comment(2)
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.Richma
@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.Rensselaerite

© 2022 - 2024 — McMap. All rights reserved.