Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using lens to add key and value to a nested Map

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!

like image 622
SkyWriter Avatar asked Dec 29 '15 12:12

SkyWriter


2 Answers

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"
like image 193
András Kovács Avatar answered Oct 05 '22 11:10

András Kovács


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.

like image 41
dfeuer Avatar answered Oct 05 '22 12:10

dfeuer