解码 case class、String 或 Int in circe

Decode case class, String or Int in circe

我使用一些 Rest API 响应 json 包含一种 "mixed" 字段。混合是指它可以采用不同类型的值。在我的例子中,ObjectStringInt 是允许的。 Object 本身由 1 Int 和 1 String.

组成

我需要解码的对象如下所示:

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

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

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

周围有这样的物体怎么处理?

假设您的情况 class 是:

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

那么我可以假设您的字段类型为:

type Mixed = X Either Int Either String

解码如下:

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))

如果您定义它们的组合方式,您可以为 Either 派生编解码器:左赢、右赢或您喜欢的任何方式:

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(_))

或者,您可以创建自己的 ADT(密封特征 + 大小写 classes),然后编写手写解码器以避免使用鉴别器字段。

底线是你必须以某种方式表达你正在解码的类型中的多态性(以一种理智的方式 - Any 不算数)然后提供一个解码器来解码它.然后你可以简单地使用它:

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

在查看@MateuszKubuszok 提供的答案之前,我最终编写了一个自定义解码器。为了完整起见,我会把它放在这里。

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())
    )

创建的方法类似,但解码器不需要 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))
}

和封闭对象:

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

运行它:

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))
}

结果:

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