为基本特征具有(密封)类型成员的密封案例类家族派生circe Codec

时间:2016-04-25 14:30:30

标签: json scala shapeless circe

我可以轻松地为密封的案例类家族推导出一个编解码器,如下所示:

import io.circe._
import io.circe.generic.auto._

sealed trait Base
case class X(x: Int) extends Base
case class Y(y: Int) extends Base

object Test extends App {
  val encoded = Encoder[Base].apply(Y(1))
  val decoded = Decoder[Base].apply(encoded.hcursor)
  println(decoded) // Right(Y(1))
}

但是,如果我将类型成员添加到基类中,我就不能再这样做了,即使它受到密封特性的限制:

import io.circe._
import io.circe.generic.auto._

sealed trait Inner
case class I1(i: Int) extends Inner
case class I2(s: String) extends Inner

sealed trait Base { type T <: Inner }
case class X[S <: Inner](x: S) extends Base { final type T = S }
case class Y[S <: Inner](y: S) extends Base { final type T = S }

object Test extends App {
  val encodedInner = Encoder[Inner].apply(I1(1))
  val decodedInner = Decoder[Inner].apply(encodedInner.hcursor) // Ok
  println(decodedInner) // Right(I1(1))

  // Doesn't work: could not find implicit for Encoder[Base] etc
  // val encoded = Encoder[Base].apply(Y(I1(1)))
  // val decoded = Decoder[Base].apply(encoded.hcursor)
  // println(decoded)
}

有没有办法可以实现我的目标?如果没有,我可以改变什么来获得类似的东西?

1 个答案:

答案 0 :(得分:1)

这不起作用的主要原因是因为你试图基本上做

Encoder[Base { type T }]

没有说明T是什么类型。这类似于期望编译该函数 -

def foo[A] = implicitly[Encoder[List[A]]]

您需要明确优化您的类型。

解决此问题的一种方法是使用Aux模式。您无法使用典型的type Aux[S] = Base { type T = S },因为在尝试派生实例时,它不会为您提供副产品(XY类无法使用从类型别名延伸)。相反,我们可以通过创建另一个密封特征Aux来破解它,并让我们的案例类从中延伸。

只要您的所有案例类都从[{1}}而不是直接从Base.Aux延伸,您就可以使用以下内容来滥用Base来安抚类型系统。

.asInstanceOf

请注意,这很大程度上取决于您实际使用类型的方式。我想你不会依赖于直接调用sealed trait Inner case class I1(i: Int) extends Inner case class I2(s: String) extends Inner sealed trait Base { type T <: Inner } object Base { sealed trait Aux[S <: Inner] extends Base { type T = S } implicit val encoder: Encoder[Base] = { semiauto.deriveEncoder[Base.Aux[Inner]].asInstanceOf[Encoder[Base]] } implicit val decoder: Decoder[Base] = { semiauto.deriveDecoder[Base.Aux[Inner]].asInstanceOf[Decoder[Base]] } } val encoded = Encoder[Base].apply(Y(I1(1))) val decoded = Decoder[Base].apply(encoded.hcursor) 而是使用Encoder[Base]并调用import io.circe.syntax._扩展方法。在这种情况下,您可以依赖于.asJson实例,该实例将根据编码/解码的值推断出来。对于您的用例而言,如果不采用Encoder[Base.Aux[S]]黑客攻击,则可能满足以下内容。

.asInstanceOf

同样,这完全取决于您如何使用实例。我怀疑你在implicit def encoder[S <: Inner : Encoder]: Encoder[Base.Aux[S]] = { semiauto.deriveEncoder } 中确实需要一个类型成员,如果你将它移动到一个通用参数,事情会更简单,所以派生者可以为你找出副产品。