Circe Decode to sealed trait extended by multiple case classes

*爱你&永不变心* 提交于 2021-02-19 06:28:06

问题


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)

回答1:


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




回答2:


I had to write own encoder and decoder for serialization/deserialization.

example,

import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._

object CirceSubtypesSerialisers {

  def main(args: Array[String]): Unit = {

    sealed trait Data

    case class OptionsData(data: Seq[String]) extends Data
    case class TextData(data: String) extends Data

    object Data {
      implicit val decodeData: Decoder[Data] = Decoder[OptionsData].map[Data](identity).or(Decoder[TextData].map[Data](identity))

      implicit val encodeData: Encoder[Data] = Encoder.instance {
        case options @ OptionsData(_) => options.asJson
        case text @ TextData(_) => text.asJson
      }
    }

    val optionsJson ="""{ "data": ["option1", "option2"] }""".stripMargin

    decode[Data](optionsJson) match {
      case Right(r: OptionsData) => println(r)
      case Left(l) => println(l)
    }


    val textJson ="""{ "data": "hey, how can i help ya?" }""".stripMargin

    decode[Data](textJson) match {
      case Right(r: TextData) => println(r)
      case Left(l) => println(l)
    }

  }

}

output:

OptionsData(List(option1, option2))
TextData(hey, how can i help ya?)

This is also mentioned in https://circe.github.io/circe/codec.html#warnings-and-known-issues and JsonCodec for sealed traits requires an explicit object definition




回答3:


Looks like you could be running into this known issue.

Per the discussion there, maybe you could try:

import io.circe.generic.JsonCodec

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

object Thing


来源:https://stackoverflow.com/questions/50457466/circe-decode-to-sealed-trait-extended-by-multiple-case-classes

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!