代码之家  ›  专栏  ›  技术社区  ›  Travis Brown

使用鉴别器对ADT案例类进行编码,即使输入为案例类

  •  4
  • Travis Brown  · 技术社区  · 7 年前

    假设我在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" }}
    

    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"}}
    

    问题是,编码器电路使用的取决于我们正在编码的表达式的静态类型。这意味着,如果我们试图直接解码其中一个case类,我们将丢失鉴别器:

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

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

    e.g. here .)

    1 回复  |  直到 7 年前
        1
  •  5
  •   Travis Brown    7 年前

    通过为只委托给 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 无法在中定义 底座 deriveEncoder 宏,导致循环定义(和堆栈溢出等)。我想我想出了一种解决这个问题的方法,在某个时候,我会尝试找到它,并把它作为另一个答案,如果我这样做。