Декодировать класс case, String или Int в circe - PullRequest
3 голосов
/ 30 мая 2020

Я использую некоторый Rest API, отвечающий json, содержащим своего рода "смешанное" поле. Под смешанным я подразумеваю, что он может принимать значения другого типа. В моем случае разрешены Object, String и Int. Сам Object состоит из 1 Int и 1 String.

Объект, который мне нужно декодировать, выглядит так:

I

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

II

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

III

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

Как поступать с такими объектами в круге?

Ответы [ 3 ]

4 голосов
/ 30 мая 2020

Подход, аналогичный тому, который был создан @ SomeName , но с декодером, не требующим 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)))
4 голосов
/ 30 мая 2020

Допустим, ваш класс case будет:

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

Тогда я мог бы предположить, что ваше поле будет иметь тип:

type Mixed = X Either Int Either String

decode, для которого может выглядеть:

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 (запечатанные признаки + классы регистра), а затем написать рукописный декодер, чтобы избежать использования поля дискриминатора.

Суть в том, что вам нужно как-то express этот полиморфизм в типе, который вы декодируете (в разумным образом - Any не считается), а затем предоставить декодер, который будет декодировать его. И тогда вы можете просто использовать его:

@JsonCodec(decodeOnly = true)
case class BigClass(field1: String, value: Mixed)
2 голосов
/ 30 мая 2020

Прежде чем посмотреть на ответ @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())
    )
...