Parse JSON array as case class with Circe

1.4k Views Asked by At

Let's use this example JSON:

{
  "bids": [
    [16182.06, 0.02994158],
    [16179.56, 0.01902097],
    [16178.05, 0.06538498]
  ],
  "asks": [
    [16191.92, 0.03597287],
    [16193.33, 0.0839688],
    [16194.95, 0.0857127]
  ]
}

Nothing special, in fact, this could be simply parsed to a case class:

@JsonCodec case class OrderBook(bids: Seq[Seq[Double]], asks: Seq[Seq[Double]])

This works great...


Problem

I actually know that every array will be of size 2, and

  • first element -> it is "rate"
  • second element -> it is the "amount"

I want to fix this with Circe and parse it to a class structure like:

@JsonCodec case class OrderBook(bids: Seq[Elems], asks: Seq[Elems])

case class Elems(rate: Double, amount: Double)

I have tried writing my Encoder and Decoder:

object Elems {
  implicit val encodeElems: Encoder[Elems] = (a: Elems) =>
    Json.arr(
      Json.fromDoubleOrNull(a.rate), Json.fromDoubleOrNull(a.amount)
    )

  implicit val decodeElems: Decoder[Elems] = (c: HCursor) =>
    for {
      f <- c.downArray.as[Seq[Double]]
    } yield {
      Elems(f(0), f(1))
    }
}

But it always fails on me with an error:

Caused by: DecodingFailure(CanBuildFrom for A, List(DownArray, DownArray, DownField(bids)))

Why? What am I doing wrong?

1

There are 1 best solutions below

0
On BEST ANSWER

downArray moves the focus from a list to its first element. When Elems.decodeElems is used, c = <[1, 2]>, so c.downArray = <1>, and then you try <1>.as[Seq[Double]], which explodes. Second, you should use as[(Double, Double)], which will decode a list like you do, but will also make sure it is exactly 2 elements long.

implicit val decodeElems: Decoder[Elems] = (c: HCursor) =>
  for {
    f <- c.as[(Double, Double)]
  } yield Elems(f._1, f._2)