【问题标题】:Encoding ADT case classes with a discriminator, even when typed as the case class使用鉴别器编码 ADT 案例类,即使键入为案例类
【发布时间】:2019-03-22 05:27:26
【问题描述】:

假设我在 Scala 中有一个 ADT:

sealed trait Base
case class Foo(i: Int) extends Base
case class Baz(x: String) extends Base

我想将此类型的值编码为如下所示的 JSON:

{ "Foo": { "i": 10000 }}
{ "Baz": { "x": "abc" }}

幸运的是,circe 的泛型派生提供了编码!

scala> import io.circe.generic.auto._, io.circe.syntax._
import io.circe.generic.auto._
import io.circe.syntax._

scala> val foo: Base = Foo(10000)
foo: Base = Foo(10000)

scala> val baz: Base = Baz("abc")
baz: Base = Baz(abc)

scala> foo.asJson.noSpaces
res0: String = {"Foo":{"i":10000}}

scala> baz.asJson.noSpaces
res1: String = {"Baz":{"x":"abc"}}

问题在于,circe 使用的编码器取决于我们正在编码的表达式的静态类型。这意味着,如果我们尝试直接解码其中一个案例类,我们会丢失判别器:

scala> Foo(10000).asJson.noSpaces
res2: String = {"i":10000}

scala> Baz("abc").asJson.noSpaces
res3: String = {"x":"abc"}

…但我想要Base 编码,即使静态类型是Foo。我知道我可以为所有案例类定义显式实例,但在某些情况下我可能有很多,我不想枚举它们。

(请注意,这是一个多次出现的问题—e.g. here。)

【问题讨论】:

    标签: json scala circe generic-derivation


    【解决方案1】:

    可以通过为仅委托给Base 解码器的基本类型的子类型定义一个实例来相当简单地做到这一点:

    import cats.syntax.contravariant._
    import io.circe.ObjectEncoder, io.circe.generic.semiauto.deriveEncoder
    
    sealed trait Base
    case class Foo(i: Int) extends Base
    case class Baz(x: String) extends Base
    
    object Base {
      implicit val encodeBase: ObjectEncoder[Base] = deriveEncoder
    }
    
    object BaseEncoders {
      implicit def encodeBaseSubtype[A <: Base]: ObjectEncoder[A] = Base.encodeBase.narrow
    }
    

    它按预期工作:

    scala> import BaseEncoders._
    import BaseEncoders._
    
    scala> import io.circe.syntax._
    import io.circe.syntax._
    
    scala> Foo(10000).asJson.noSpaces
    res0: String = {"Foo":{"i":10000}}
    
    scala> (Foo(10000): Base).asJson.noSpaces
    res1: String = {"Foo":{"i":10000}}
    

    不幸的是,encodeBaseSubtype 不能在 Base 伴随对象中定义,因为那时它会被 deriveEncoder 宏拾取,导致循环定义(和堆栈溢出等)。我想我在某个时候为这个问题想出了一种可怕的解决方法——如果我这样做了,我会尝试找到它并将其作为另一个答案发布。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-02-17
      • 1970-01-01
      • 2015-11-11
      • 2019-03-15
      • 1970-01-01
      • 2011-04-07
      • 1970-01-01
      相关资源
      最近更新 更多