mr_fred
mr_fred

Reputation: 61

CIRCE: How to decode a json model with disjunction in a field type

The application I am developing has to decode json from a data source which for a given field may return either a List[String] or a List[Double]. I want to decode this json into a case class so i can process the data.

[{
    "id": 123,
    "latlng": ["-12.777", "18.776"]
}, {
    "id": 123,
    "latlng": [-12.777, 18.776]
}]

Im using circe 0.11.1

currently my case class looks like

case class Report(id:Int, latlng:Either[List[String],List[Double]])

and my decoding code

 decode[List[Report]](testData) 

i receive the error

DecodingFailure at [0].latlng: [A, B]Either[A, B]

Upvotes: 1

Views: 241

Answers (1)

Allen Han
Allen Han

Reputation: 1163

It sounds like you already have a solution that works for you. I found a solution to your original problem though. It is not very elegant, but it works. There might be a more elegant solution that involves the Validated monad in Cats library, but I am not familiar enough with the Cats library to write it in that way.

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

object Main {

  def main(args: Array[String]) = {
    val testData =
      """
        |[{
        |    "id": 123,
        |    "latling": ["-12.777", "18.776"]
        |}, {
        |    "id": 123,
        |    "latling": [-12.777, 18.776]
        |}]
      """.stripMargin

    println(decode[List[Report]](testData))

  }

  case class Report(id: Int, latling: Either[List[String],List[Double]])

  object Report {
    implicit val reportDecoder: Decoder[Report] = new Decoder[Report] {
      override def apply(c: HCursor): Decoder.Result[Report] = {
        val stringAttempt = for {
          id <- c.downField("id").as[Int]
          latlingString <- c.downField("latling").as[List[String]]
        } yield Report(id, Left(latlingString))

        val doubleAttempt = for {
          id <- c.downField("id").as[Int]
          latlingDouble <- c.downField("latling").as[List[Double]]
        } yield Report(id, Right(latlingDouble))

        stringAttempt match {
          case Right(stringValue) => Right(stringValue)
          case Left(stringFailure) => doubleAttempt
        }
      }
    }
  }
}

You can run by starting up sbt with

sbt

and running

runMain Main

Here is my build.sbt file:

name := "stackoverflow20190821"

version := "0.1"

scalaVersion := "2.12.0"

val circeVersion = "0.11.1"

libraryDependencies ++= Seq(
  "io.circe" %% "circe-core",
  "io.circe" %% "circe-generic",
  "io.circe" %% "circe-parser"
).map(_ % circeVersion)

Upvotes: 1

Related Questions