Search code examples
scalacirce

Circe Decode to sealed trait extended by multiple case classes


I've seen similar questions before, but none of them have worked. I think that they ask something different so I'm asking here. I have something like this in one file:

sealed trait Thing
case class SomeThing() extends Thing
case class OtherThing() extends Thing

and in another file:

val str = //valid json
val decoded = decode[Thing](str)
println(decoded)

and I get:

Left(DecodingFailure(...))

This works if I did:

val str = //valid json
val decoded = decode[SomeThing](str)
println(decoded)

Solution

  • you can use circe-generic-extras and add type discriminator for your jsons:

    In your build.sbt:

    libraryDependencies ++= Seq(
      "io.circe" %% "circe-generic-extras" % "0.12.2",
      "io.circe" %% "circe-parser" % "0.12.2"
    )
    

    Now let's define our classes and serialize + deserialize them:

    sealed trait Thing
    case class SomeThing() extends Thing
    case class OtherThing() extends Thing
    
    import io.circe.generic.extras.auto._
    import io.circe.generic.extras.Configuration
    import io.circe.syntax._
    import io.circe.parser
    
    implicit val customConfig: Configuration =
      Configuration.default.withSnakeCaseMemberNames.withDiscriminator("type")
    
    val thing: Thing = SomeThing()
    
    // serialize thing to json
    val jsString: String = thing.asJson.spaces2
    println(s"serialized $thing to:\n$jsString")
    /* serialized SomeThing() to:
    {
    "type" : "SomeThing"
    }
    */
    
    // deserialize json to thing
    val errorOrMyTrait: Either[io.circe.Error, Thing] = parser.decode[Thing](jsString)
    
    println(errorOrMyTrait) // Right(SomeThing())
    

    Notice that now the serialized json contains the type of the sealed class