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

Gio*_*tti 7 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))
}
Run Code Online (Sandbox Code Playgroud)

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

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)
}
Run Code Online (Sandbox Code Playgroud)

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

pyr*_*ade 1

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

Encoder[Base { type T }]
Run Code Online (Sandbox Code Playgroud)

不用说T是什么类型。这类似于期望该函数编译 -

def foo[A] = implicitly[Encoder[List[A]]]
Run Code Online (Sandbox Code Playgroud)

您需要明确地细化您的类型。

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

只要您的所有案例类都从而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)
Run Code Online (Sandbox Code Playgroud)

请注意,这很大程度上取决于您实际使用类型的方式。我想您不会依赖Encoder[Base]直接调用,而是使用import io.circe.syntax._并调用.asJson扩展方法。在这种情况下,您可以依赖一个Encoder[Base.Aux[S]]根据编码/解码的值推断的实例。像下面这样的东西可能足以满足您的用例,而无需诉诸.asInstanceOf黑客。

implicit def encoder[S <: Inner : Encoder]: Encoder[Base.Aux[S]] = {
  semiauto.deriveEncoder
}
Run Code Online (Sandbox Code Playgroud)

同样,这完全取决于您如何使用实例。我怀疑您实际上需要 中的类型成员Base,如果您将其移至通用参数,那么事情会更简单,以便派生者可以为您计算出余积。