cpard cpard - 6 months ago 24
JSON Question

How to modify value and type in JSON using argonaut lens?

Assume the following simple JSON document:

"key" : "val1"

I'd like to update the value of "key" but at the same time also change its type, so from string change it to an int. Now, using an HCursor like below it is possible and straight forward to do it:

val cursor = js.hcursor
val position = (cursor --\ "key") >-> (_ => jNumber(1))

By "undoing" the above position I end up having a new json where "key" has a numerical value and not a String, which is perfect.

Is it possible to do the same using lenses? I tried to do the following:

val lense = jObjectPL >=>
jsonObjectPL("key") >=>
lense.mod(_ => JsonBigDecimal(1), js)

But although I do not get an error it also doesn't work, at the end I end up with the original json document unmodified. If I respect the datatype though, things work as they should. Is there a reason that lenses should be used for modifications of the same datatype only? Or I'm just doing something terribly wrong :)


Nope, nothing terribly wrong—you're almost there. The issue is that this path:

jObjectPL >=> jsonObjectPL("key") >=> jNumberPL

Navigates to a JSON number at "key". Your js doesn't have a JSON number at key, so the lens doesn't point to anything, and the modification doesn't affect anything.

You can fix this by just removing the last step from the lens:

val lens = jObjectPL >=> jsonObjectPL("key")

This just navigates to the "key" field but doesn't put constraints on what kind of JSON value it is. Then you can change it to whatever you want:

scala> val lens = jObjectPL >=> jsonObjectPL("key")
lens: scalaz.PLensFamily[...

scala> lens.mod(_ => jNumber(JsonBigDecimal(1)), js)
res0: argonaut.Json = {"key":1}

Note that since the lens is pointing to a Json value, not a JsonNumber, you'll have to wrap the JsonBigDecimal in jNumber to make the types line up.