如何在不区分对象的情况下带圆圈解码ADT

如何在不区分对象的情况下带圆圈解码ADT,第1张

如何在不区分对象的情况下带圆圈解码ADT 枚举ADT构造函数

获得所需表示的最直接方法是对案例类使用通用派生,但对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
表明我们不需要一个额外的字段来指示构造函数,从而为我们提供了与上一节中的圆形实例相同的行为。

如果您有兴趣看到这种情况,请提出问题,或者(甚至更好)提出请求。:)



欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5639412.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存