Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why it does not decode to ADT type?

Tags:

scala

circe

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.

like image 352
softshipper Avatar asked Dec 14 '22 11:12

softshipper


2 Answers

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.

like image 128
Travis Brown Avatar answered Jan 08 '23 13:01

Travis Brown


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()))
like image 39
Dmytro Mitin Avatar answered Jan 08 '23 15:01

Dmytro Mitin