简体   繁体   中英

Decode case class, String or Int in circe

I use some Rest API responding with json containing a sort of "mixed" field. By mixed I mean that it can take values of a different type. In my case Object , String and Int are allowed. The Object itself consists of 1 Int and 1 String .

The object I need to decode looks as:

I

{
   field1: 32,
   ...
   value: {
      id: 23,
      text: "text"
   }
}

II

{
   field1: 32,
   ...
   value: 21
}

III

{
   field1: 32,
   ...
   value: "value"
}

How to deal with such objects in circe?

Let's say your case class would be:

@JsonCodec(decodeOnly = true)
case class X(id: Int, text: String)

Then I could assume that your field would be of type:

type Mixed = X Either Int Either String

decode for which could look like:

implicit val mixedDecoder: Decoder[Mixed] = 
  Decoder[X].map[Mixed](x => Left(Left(x))) or Decoder[Int].map[Mixed](i => Left(Right(i))) or Decoder[String].map[Mixed](s => Right(s))

You could derive codecs for Either if you define how they would be combined: left wins, right wins, or whatever you prefer:

implicit def eitherDecode[L: Decoder, R: Decoder]: Decoder[L Either R] =
  Decoder[L].map[L Either R](Left(_)) or Decoder[R].map[L Either R](Right(_))

Alternatively, you can create your own ADT (sealed trait + case classes) and then writing handwritten decoder to avoid using discriminator field.

The bottom line is that you have to somehow express that polymorphism in the type you are decoding into (in a sane manner - Any doesn't count) and then provide a decoder that would decode into it. And then you could simply use it:

@JsonCodec(decodeOnly = true)
case class BigClass(field1: String, value: Mixed)

A similar approach to what @SomeName created, but with a decoder not requiring HCursor :

sealed trait Value
object Value {
  final case class Values(id: Int, text: String) extends Value
  final case class IntValue(i: Int) extends Value
  final case class StringValue(s: String) extends Value

  implicit val valueDecoder: Decoder[Value] = Decoder[String]
    .map[Value](StringValue)
    .or(Decoder[Int].map[Value](IntValue))
    .or(Decoder.forProduct2("id", "text")(Values.apply).map[Value](identity))
}

And the enclosing object:

final case class Example(field1: Int, value: Value)
object Example {
  implicit val exampDecoder: Decoder[Example] =
    Decoder.forProduct2("field1", "value")(Example.apply)
}

Run it:

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

def main(args: Array[String]): Unit = {
  val fst =
    """
      |{
      |   "field1": 32,
      |   "value": {
      |      "id": 23,
      |      "text": "text"
      |   }
      |}""".stripMargin

  val snd =
    """
      |{
      |   "field1": 32,
      |   "value": 21
      |}
      |""".stripMargin

  val third =
    """{
      |   "field1": 32,
      |   "value": "value"
      |}
      |""".stripMargin

  println(decode[Example](fst))
  println(decode[Example](snd))
  println(decode[Example](third))
}

Results:

Right(Example(32,Values(23,text)))
Right(Example(32,IntValue(21)))
Right(Example(32,StringValue(value)))

Before looking at the answer provided by @MateuszKubuszok I ended up in writing a custom decoder. I will put it here for completeness.

sealed trait SomeValue
final case class SomeObjValue(id: Int, text: String) extends SomeValue
final case class SomeIntValue(int: Int) extends SomeValue
final case class SomeStringValue(str: String) extends SomeValue

implicit def someValueDecode: Decoder[SomeValue] =
  (cursor: HCursor) =>
    if (cursor.value.isObject) Decoder[SomeObjValue].apply(cursor)
    else if (cursor.value.isString) cursor.value.as[String].map(SomeStringValue)
    else if (cursor.value.isNumber) cursor.value.as[Int].map(SomeIntValue)
    else
      Decoder.resultInstance.raiseError(
      DecodingFailure(s"${cursor.value} is not supported for decoding", List())
    )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM