Chris Martin
Chris Martin

Reputation: 30736

Mapping over a JSON array with Argonaut

I'm having a hard time slogging through the Argonaut documentation, so I figured I'd just ask for a simple example.

val input = """{"a":[{"b":4},{"b":5}]}"""

val output = ??? // desired value: List(4, 5)

I can get a cursor down to the array:

Parse.parse(input).map((jObjectPL >=> jsonObjectPL("a") >=> jArrayPL)(_))
// scalaz.\/[String,Option[scalaz.IndexedStore[argonaut.Argonaut.JsonArray,
//  argonaut.Argonaut.JsonArray,argonaut.Json]]] =
// \/-(Some(IndexedStoreT((<function1>,List({"b":4}, {"b":5})))))

But then what? Am I on the right track? Should I even be using cursors for this?

Edit - Here's some progress, I guess. I've written a decoder for the list:

Parse.parse("""[{"b": 4}, {"b": 5}]""")
  .map(_.as(IListDecodeJson(DecodeJson(_.downField("b").as[Int]))))
// scalaz.\/[String,argonaut.DecodeResult[scalaz.IList[Int]]] =
// \/-(DecodeResult(\/-([4,5])))

Edit - Slowly starting to put it together...

Parse.parse(input).map(_.as[HCursor].flatMap(_.downField("a").as(
  IListDecodeJson(DecodeJson(_.downField("b").as[Int])))))
// scalaz.\/[String,argonaut.DecodeResult[scalaz.IList[Int]]] =
// \/-(DecodeResult(\/-([4,5])))

Edit - So I guess my best solution so far is:

Parse.parse(input).map(_.as(
  DecodeJson(_.downField("a").as(
    IListDecodeJson(DecodeJson(_.downField("b").as[Int])).map(_.toList)
  ))
))

Feels a bit verbose, though.

Upvotes: 4

Views: 1560

Answers (2)

jamakkon
jamakkon

Reputation: 1

This case-classing might not be the way you want to go, but here's my 2 cents.

case class B(b: Int)
object B {
  implicit def BCodecJson: CodecJson[B] =
  casecodec1(B.apply, B.unapply)("b")
}

val jsonString = """{"a":[{"b":4},{"b":5}]}"""
val vals: List[Int] = Parse.parseOption(jsonString).map { json: Json =>
    (json -|| List("a")).flatMap(_.as[List[B]].value).getOrElse(Nil).map(_.b)
  }.getOrElse(Nil)

I guess that does it.

Upvotes: 0

Travis Brown
Travis Brown

Reputation: 139028

You can do this pretty nicely with the new-ish Monocle support in Argonaut (I'm using Argonaut master here, since the 6.1 milestones are still on Monocle 0.5):

import argonaut._, Argonaut._
import scalaz._, Scalaz._
import monocle._, Monocle._

val lens =
  Parse.parseOptional ^<-? 
  jObjectPrism        ^|-?
  index("a")          ^<-?
  jArrayPrism         ^|->>
  each                ^<-?
  jObjectPrism        ^|-?
  index("b")          ^<-?
  jIntPrism

And then:

scala> lens.getAll("""{"a":[{"b":4},{"b":5}]}""")
res0: scalaz.IList[Int] = [4,5]

The operators look horrible at first, but you get used to them, and the composed pieces read pretty naturally. And of course since this is a lens there are all kinds of operations that you can use in addition to getAll.

Upvotes: 9

Related Questions