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)))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With