Search code examples
haskellhaskell-lensaesonlenses

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!


Solution

  • 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"