Search code examples
scalascalazlensesargonaut

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!


Solution

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