FintanH FintanH - 16 days ago 10
Scala Question

Extracting values from JSON Array of JSON Objects using Argonaut Lenses

It's my first time using argonauts and only have a small knowledge of lenses (enough to get by). I've spent a while trying to figure out the problem myself but getting nowhere.

I'm trying to build a lens to get a JsonArray (of Strings) from some JSON. I can get as far as the Object that has the Array but not sure what to do from there.

The JSON looks like:

json example

And my lens so far is this:

val hashtagsView = twitterEntitiesView >=> jsonObjectPL("hashtags") >=> jArrayPL


I'm not sure if that
jArrayPL
is correct either. What I would like to do is just retrieve the text from the Array.

So to wrap up, can anyone help me in finding out how to construct a lens that looks into hashtags and then for each element of the array look into the text, finally getting a values as a
JsonArray
.

Update:

With some help from Travis I have the following code compiling:

import argonaut._, Argonaut._
import monocle.std.list._, monocle.function.Each.each, monocle.function.Index.index
import scalaz._, Scalaz._

val \/-(json) = Parse.parse(rawJSON)
val lens = jObjectPrism
.composeOptional(index("hashtags"))
.composePrism(jArrayPrism)
.composeTraversal(each[List[Json], Json])
.composePrism(jObjectPrism)
.composeOptional(index("text"))
.composePrism(jStringPrism)

println(lens.getAll(json))


Unfortunately, I get a runtime error:
scalaz.Scalaz$.ToEitherOps(Ljava/lang/Object;)Lscalaz/syntax/EitherOps;
starting at the line
val \/-(json) = Parse.parse(rawJSON)


Thanks in advance!

Answer

Are you willing to use the Monocle lenses that Argonaut provides instead of the Scalaz lenses? If so, working with traversals is a lot nicer:

import argonaut._, Argonaut._
import monocle.function.{ each, index }, monocle.std.list._
import scalaz._, Scalaz._

val doc = """{
  "hashtags": [
    { "indices": [0, 3], "text": "foo" },
    { "indices": [3, 6], "text": "bar" }
  ]
}"""

val \/-(json) = Parse.parse(doc)

val lens = jObjectPrism
  .composeOptional(index("hashtags"))
  .composePrism(jArrayPrism)
  .composeTraversal(each[List[Json], Json])
  .composePrism(jObjectPrism)
  .composeOptional(index("text"))
  .composePrism(jStringPrism)

And then:

scala> lens.getAll(json)
res0: List[argonaut.Argonaut.JsonString] = List(foo, bar)

scala> lens.modify(_ + " (new)")(json).spaces2
res1: String =
{
  "hashtags" : [
    {
      "indices" : [
        0,
        3
      ],
      "text" : "foo (new)"
    },
    {
      "indices" : [
        3,
        6
      ],
      "text" : "bar (new)"
    }
  ]
}

And so on. You could do something similar with Scalaz lenses but it would take more work.