获得所需表示的最直接方法是对案例类使用通用派生,但对ADT类型使用明确定义的实例:
import cats.syntax.functor._import io.circe.{ Deprer, Enprer }, io.circe.generic.auto._import io.circe.syntax._sealed trait Eventcase class Foo(i: Int) extends Eventcase class Bar(s: String) extends Eventcase class Baz(c: Char) extends Eventcase class Qux(values: List[String]) extends Eventobject Event { implicit val enpreEvent: Enprer[Event] = Enprer.instance { case foo @ Foo(_) => foo.asJson case bar @ Bar(_) => bar.asJson case baz @ Baz(_) => baz.asJson case qux @ Qux(_) => qux.asJson } implicit val depreEvent: Deprer[Event] = List[Deprer[Event]]( Deprer[Foo].widen, Deprer[Bar].widen, Deprer[Baz].widen, Deprer[Qux].widen ).reduceLeft(_ or _)}
请注意,由于类型类不是协变的,因此必须在解码器上调用
widen(由Cats的
Functor语法提供,这在第一次导入时就纳入了范围)
Deprer。circe类型类的不变性是一个有争议的问题(例如,Argonaut已从不变性变为协变并返回),但是它具有足够的好处,因此不太可能更改,这意味着我们有时需要这种解决方法。
另外值得一提的是我们的明确
Enprer和
Deprer实例将优先于一般衍生的情况下,我们会以其他方式从一开始
io.circe.generic.auto._进口(见我的幻灯片在这里为如何优先工作的一些讨论)。
我们可以像这样使用这些实例:
scala> import io.circe.parser.depreimport io.circe.parser.deprescala> depre[Event]("""{ "i": 1000 }""")res0: Either[io.circe.Error,Event] = Right(Foo(1000))scala> (Foo(100): Event).asJson.noSpacesres1: String = {"i":100}
这是可行的,并且如果您需要能够指定尝试ADT构造函数的顺序,则它是当前最佳的解决方案。即使我们免费获得case类实例,必须枚举这样的构造函数显然也不理想。
更通用的解决方案正如我在Gitter上所指出的,我们可以通过使用circe-
shapes模块来避免写出所有情况的麻烦:
import io.circe.{ Deprer, Enprer }, io.circe.generic.auto._import io.circe.shapesimport shapeless.{ Coproduct, Generic }implicit def enpreAdtNoDiscr[A, Repr <: Coproduct](implicit gen: Generic.Aux[A, Repr], enpreRepr: Enprer[Repr]): Enprer[A] = enpreRepr.contramap(gen.to)implicit def depreAdtNoDiscr[A, Repr <: Coproduct](implicit gen: Generic.Aux[A, Repr], depreRepr: Deprer[Repr]): Deprer[A] = depreRepr.map(gen.from)sealed trait Eventcase class Foo(i: Int) extends Eventcase class Bar(s: String) extends Eventcase class Baz(c: Char) extends Eventcase class Qux(values: List[String]) extends Event
然后:
scala> import io.circe.parser.depre, io.circe.syntax._import io.circe.parser.depreimport io.circe.syntax._scala> depre[Event]("""{ "i": 1000 }""")res0: Either[io.circe.Error,Event] = Right(Foo(1000))scala> (Foo(100): Event).asJson.noSpacesres1: String = {"i":100}
这对于任何ADT任何地方工作,
enpreAdtNoDiscr并且
depreAdtNoDiscr都在范围之内。如果我们希望它受到更大的限制,则可以
A在这些定义中用ADT类型替换泛型,或者可以使定义成为非隐式的,并明确定义要以这种方式编码的ADT的隐式实例。
这种方法的主要缺点(除了额外的圆形依赖)是,构造函数将按字母顺序尝试,如果我们的模版类模棱两可(成员名称和类型相同,则可能不是我们想要的) )。
未来通用扩展模块在这方面提供了更多的可配置性。例如,我们可以编写以下代码:
import io.circe.generic.extras.auto._import io.circe.generic.extras.Configurationimplicit val genDevConfig: Configuration = Configuration.default.withDiscriminator("what_am_i")sealed trait Eventcase class Foo(i: Int) extends Eventcase class Bar(s: String) extends Eventcase class Baz(c: Char) extends Eventcase class Qux(values: List[String]) extends Event
然后:
scala> import io.circe.parser.depre, io.circe.syntax._import io.circe.parser.depreimport io.circe.syntax._scala> (Foo(100): Event).asJson.noSpacesres0: String = {"i":100,"what_am_i":"Foo"}scala> depre[Event]("""{ "i": 1000, "what_am_i": "Foo" }""")res1: Either[io.circe.Error,Event] = Right(Foo(1000))
代替JSON中的包装器对象,我们有一个额外的字段来指示构造函数。这不是默认行为,因为它有一些奇怪的极端情况(例如,如果我们的一个案例类有一个名为的成员
what_am_i),但是在许多情况下,这是合理的,并且自引入该模块以来,它在通用扩展中得到了支持。
这仍然不能完全满足我们的需求,但是比默认行为要近。我还一直在考虑
withDiscriminator采用a
Option[String]代替a
String,以
None表明我们不需要一个额外的字段来指示构造函数,从而为我们提供了与上一节中的圆形实例相同的行为。
如果您有兴趣看到这种情况,请提出问题,或者(甚至更好)提出请求。:)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)