为什么不将其解码为ADT类型?

zer*_*ing 3 scala circe

我试图将以下字符串派生为适当的ADT类型:

res6: String = {"raw":"Hello","status":{"MsgSuccess":{}}} 
Run Code Online (Sandbox Code Playgroud)

并使用circe库。

ADT类型如下所示:

sealed trait MsgDoc {
}

final case class MsgPreFailure(raw: String, reasons: Chain[String]) extends MsgDoc

final case class MsgProceed(raw: String, status: MsgStatus) extends MsgDoc
Run Code Online (Sandbox Code Playgroud)

MsgStatus类型:

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
Run Code Online (Sandbox Code Playgroud)

和方式,我试着开车:

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)
}
Run Code Online (Sandbox Code Playgroud)

和执行:

object Main extends App {


  val json = MsgProceed("Hello", MsgSuccess).asJson
  println(json)
  val adt = decode[MsgDoc](json.noSpaces)
  println(adt)

} 
Run Code Online (Sandbox Code Playgroud)

结果,我得到了:

{
  "raw" : "Hello",
  "status" : {
    "MsgSuccess" : {

    }
  }
}
Left(DecodingFailure(CNil, List())) 
Run Code Online (Sandbox Code Playgroud)

如您所见,它不decode正确。

可以找到源代码https://gitlab.com/playscala/adtjson

Tra*_*own 7

我不太确定这些MsgDocDerivation东西打算做什么—似乎没有必要并且分散注意力—但是我认为关键问题在于,circ的编码(和解码)是由静态类型驱动的,而不是被编码值的运行时类(或已解码)。这意味着以下两个JSON值将不同:

val value = MsgProceed("Hello", MsgSuccess)

val json1 = value.asJson
val json2 = (value: MsgDoc).asJson
Run Code Online (Sandbox Code Playgroud)

在您的情况下,以下对我来说很好:

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)
Run Code Online (Sandbox Code Playgroud)

请注意,这json与您看到的有所不同:

scala> json
res0: io.circe.Json =
{
  "MsgProceed" : {
    "raw" : "Hello",
    "status" : {
      "MsgSuccess" : {

      }
    }
  }
}

scala> backToValue
res1: Either[io.circe.Error,MsgDoc] = Right(MsgProceed(Hello,MsgSuccess))
Run Code Online (Sandbox Code Playgroud)

这是因为我执行了从MsgProceed到(类型安全)转换MsgDoc。无论如何,这通常就是您使用ADT的方式-您不会传递静态类型为case类子类型的值,而是sealed trait基础类型。

  • @zero_coding你能解释这个问题吗?通用派生使您避免写出类型类实例而无需使用运行时反射或以其他方式放弃类型安全性。 (2认同)
  • @zero_coding在这里,Circe为您执行了通用派生,因此您不必通过Shapeless手动进行。 (2认同)