我可以轻松地为密封的案例类家族推导出一个编解码器,如下所示:
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)
}
有没有办法可以实现我的目标?如果没有,我可以改变什么来获得类似的东西?
答案 0 :(得分:1)
这不起作用的主要原因是因为你试图基本上做
Encoder[Base { type T }]
没有说明T
是什么类型。这类似于期望编译该函数 -
def foo[A] = implicitly[Encoder[List[A]]]
您需要明确优化您的类型。
解决此问题的一种方法是使用Aux
模式。您无法使用典型的type Aux[S] = Base { type T = S }
,因为在尝试派生实例时,它不会为您提供副产品(X
和Y
类无法使用从类型别名延伸)。相反,我们可以通过创建另一个密封特征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
}
中确实需要一个类型成员,如果你将它移动到一个通用参数,事情会更简单,所以派生者可以为你找出副产品。