I am trying to derive the following string into a proper ADT type:
res6: String = {"raw":"Hello","status":{"MsgSuccess":{}}}
and using the circe library.
The ADT type looks as the following:
sealed trait MsgDoc {
}
final case class MsgPreFailure(raw: String, reasons: Chain[String]) extends MsgDoc
final case class MsgProceed(raw: String, status: MsgStatus) extends MsgDoc
and the MsgStatus
type:
sealed trait MsgStatus {
}
case object MsgSuccess extends MsgStatus
final case class MsgFailure(reasons: Chain[String]) extends MsgStatus
final case class MsgUnknown(reason: String) extends MsgStatus
and the way, I've tried to drive:
object MsgDocDerivation {
import shapeless.{Coproduct, Generic}
implicit def encodeAdtNoDiscr[A, Repr <: Coproduct](implicit
gen: Generic.Aux[A, Repr],
encodeRepr: Encoder[Repr]
): Encoder[A] = encodeRepr.contramap(gen.to)
implicit def decodeAdtNoDiscr[A, Repr <: Coproduct](implicit
gen: Generic.Aux[A, Repr],
decodeRepr: Decoder[Repr]
): Decoder[A] = decodeRepr.map(gen.from)
}
and the execution:
object Main extends App {
val json = MsgProceed("Hello", MsgSuccess).asJson
println(json)
val adt = decode[MsgDoc](json.noSpaces)
println(adt)
}
as the result I've got:
{
"raw" : "Hello",
"status" : {
"MsgSuccess" : {
}
}
}
Left(DecodingFailure(CNil, List()))
As you can see, it does not decode
properly.
The source code can be find https://gitlab.com/playscala/adtjson.
I'm not really sure what the MsgDocDerivation
stuff is intended to do—it seems unnecessary and distracting—but I think the key problem is that circe's encoding (and decoding) is driven by static types, not the runtime class of the value being encoded (or decoded). This means that the following two JSON values will be different:
val value = MsgProceed("Hello", MsgSuccess)
val json1 = value.asJson
val json2 = (value: MsgDoc).asJson
In your case the following works just fine for me:
import cats.data.Chain
sealed trait MsgStatus
case object MsgSuccess extends MsgStatus
final case class MsgFailure(reasons: Chain[String]) extends MsgStatus
final case class MsgUnknown(reason: String) extends MsgStatus
sealed trait MsgDoc
final case class MsgPreFailure(raw: String, reasons: Chain[String]) extends MsgDoc
final case class MsgProceed(raw: String, status: MsgStatus) extends MsgDoc
import io.circe.generic.auto._, io.circe.jawn.decode, io.circe.syntax._
val value: MsgDoc = MsgProceed("Hello", MsgSuccess)
val json = value.asJson
val backToValue = decode[MsgDoc](json.noSpaces)
Note that json
is different from what you were seeing:
scala> json
res0: io.circe.Json =
{
"MsgProceed" : {
"raw" : "Hello",
"status" : {
"MsgSuccess" : {
}
}
}
}
scala> backToValue
res1: Either[io.circe.Error,MsgDoc] = Right(MsgProceed(Hello,MsgSuccess))
This is because I've performed a (typesafe) upcast from MsgProceed
to MsgDoc
. This is typically how you work with ADTs, anyway—you're not passing around values statically typed as the case class subtypes, but rather as the sealed trait
base type.
The thing is that MsgProceed
as of type MsgProceed
and as of type MsgDoc
is encoded to different jsons. So you try to decode MsgDoc
from wrong json.
val json = MsgProceed("Hello", MsgSuccess).asJson
println(json)
//{
// "raw" : "Hello",
// "status" : {
// "MsgSuccess" : {
//
// }
// }
//}
val json1 = (MsgProceed("Hello", MsgSuccess): MsgDoc).asJson
println(json1)
//{
// "MsgProceed" : {
// "raw" : "Hello",
// "status" : {
// "MsgSuccess" : {
//
// }
// }
// }
//}
val adt0 = decode[MsgProceed](json.noSpaces)
println(adt0)
//Right(MsgProceed(Hello,MsgSuccess))
val adt1 = decode[MsgDoc](json1.noSpaces)
println(adt1)
//Right(MsgProceed(Hello,MsgSuccess))
val adt = decode[MsgDoc](json.noSpaces)
println(adt)
//Left(DecodingFailure(CNil, List()))
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