ceran
ceran

Reputation: 1412

Circe: decoding a container type with different possible content types

My goal is to transform JSON into the following model:

case class Container(typeId: Int, timestamp: Long, content: Content)

sealed trait Content
case class ContentType1(...) extends Content
case class ContentType2(...) extends Content
case class ContentType3(...) extends Content

What would be a good way to decode that with circe? I guess automatic decoding does not work in this case.

edit: the documentation of the json structure describes the content field as ?mixed (object, integer, bool), so it can also be a simple Int or Boolean instead of a case class object. But for now it would be ok to ignore these two types (although it would be nice to have a solution for that).

Upvotes: 4

Views: 1345

Answers (1)

Reactormonk
Reactormonk

Reputation: 21730

I'd use semiauto (with deriveDecoder), validate and or.

case class Container[+C](typeId: Int, timestamp: Long, content: C)

sealed trait Content
@JsonCodec case class ContentType1(...) extends Content
@JsonCodec case class ContentType2(...) extends Content

object Content {
  import shapeless._
  import io.circe._
  import io.circe.generic.semiauto._

  def decodeContentWithGuard[T: Decoder](number: Int): Decoder[Content[T]] = {
    deriveDecoder[Content[T]].validate(_.downField("typeId") == Right(number), s"wrong type number, expected $number")
  }

  implicit val contentDecoder: Decoder[Container[Content]] = {
    decodeWithGuard[ContentType1](1) or
    decodeWithGuard[ContentType2](2)
  }
}

If you don't want the covariant annotation, you'll have to map and upcast the inner ContentTypeX to Content.

There's probably also a solution without the generic, but then you can't use semiauto.

Might not compile, didn't test it.

Upvotes: 0

Related Questions