How to decode array containing json with Circe

643 views Asked by At

I have my circe Decoder as shown below. I am confident my Sentiment Decoder works correctly so won't include it below.

case class CryptoData(value: String, valueClassification: Sentiment)
  implicit val decoder: Decoder[CryptoData] = Decoder.instance { json =>
    for {
      value               <- json.downField("data").get[String]("value")
      valueClassification <- json.downField("data").get[Sentiment]("value_classification")
    } yield CryptoData(value, valueClassification)
  }

my Json looks like this

{
  "name" : "Fear and Greed Index",
  "data" : [
    {
      "value" : "31",
      "value_classification" : "Fear",
      "timestamp" : "1631318400",
      "time_until_update" : "54330"
    }
  ],
  "metadata" : {
    "error" : null
  }
}

I simply want value and value_classification. As can be seen, those values sit within an array.

I suspect Circe is looking to decode a List[data] but I don't want to create a case class DataInfo(list: List[Data]) it just doesn't feel right.

1

There are 1 answers

0
Boris Azanov On BEST ANSWER

You just missed a downArray call to parse data as the array of objects. Working decoder:

implicit val cryptoDecoder: Decoder[CryptoData] = Decoder.instance { json =>
  val data = json.downField("data").downArray
  for {
    value <- data.get[String]("value")
    valueClassification <- data.get[Sentiment]("value_classification")
  } yield CryptoData(value, valueClassification)
}

Small recomendataion:

I would advise you to define a basic decoder for CryptoData, it should just decode CryptoData from the data object:

{
    "value" : "31",
    "value_classification" : "Fear",
    "timestamp" : "1631318400",
    "time_until_update" : "54330"
}

to:

CryptoData("31", Fear)

and if you have some extended JSON, you can just move down to the actual CryptoData field using some custom parser and parse object.

full code:

import io.circe
import io.circe.Decoder
import io.circe.parser._

trait Sentiment

object Sentiment {
  case object Fear extends Sentiment

  implicit val sentimentDecoder: Decoder[Sentiment] = Decoder.decodeString.map {
    case "Fear" => Fear
  }
}

case class CryptoData(value: String, valueClassification: Sentiment)

object CryptoData {
  implicit val cryptoDecoder: Decoder[CryptoData] = Decoder.instance { json =>
    for {
      value <- json.downField("value").as[String]
      valueClassification <- json.downField("value_classification").as[Sentiment]
    } yield CryptoData(value, valueClassification)
  }

  def decodeRaw(extendedObject: String): Either[circe.Error, Array[CryptoData]] =
    parse(extendedObject).flatMap(json => json.hcursor.downField("data").as[Array[CryptoData]])
}

testing:

val extendedJson =
  """
    |{
    |  "name" : "Fear and Greed Index",
    |  "data" : [
    |    {
    |      "value" : "31",
    |      "value_classification" : "Fear",
    |      "timestamp" : "1631318400",
    |      "time_until_update" : "54330"
    |    }
    |  ],
    |  "metadata" : {
    |    "error" : null
    |  }
    |}
    |""".stripMargin

// here should be Array
val result: Either[circe.Error, Array[CryptoData]] = CryptoData.decodeRaw(extendedJson)
// Right(CryptoData(31,Fear))
println(result.map(_.mkString(", ")))


val cryptoDataJson =
  """
    |{
    |      "value" : "31",
    |      "value_classification" : "Fear",
    |      "timestamp" : "1631318400",
    |      "time_until_update" : "54330"
    |    }
    |""".stripMargin

// Right(CryptoData(31,Fear))
println(decode[CryptoData](cryptoDataJson))