通过判别器解码嵌套有Coproduct的Case类

时间:2019-04-11 15:15:57

标签: scala shapeless circe

我进行了以下设置

case class A(eventType : String, fieldOne : Int)
case class B(eventType : String, fieldOne : Int, fieldTwo : Int)

type Event = A :+: B :+: CNil

case class X(id :String, events : List[Event])

我收到以下Json消息,一个带有一个事件的X(B的实例)

{
"id" : "id",
"events" : [
    {
      "eventType" : "B",
      "fieldOne": 1,
      "fieldTwo" : 2
    }
]
}

如果我使用circe,我可以将其解码为X的实例,但是在事件列表中,因为A在联乘积中首先到达,它将解码为A。

val actual = X("id", [A("B", 1)])
val expected = X("id", [B("B", 1, 2)])

我希望能够使用eventType作为配置区分符来确定嵌套字段在Coproduct中的类型。

我认为答案就在这里

Generic derivation for ADTs in Scala with a custom representation

但是我似乎不太适合我的情况。

1 个答案:

答案 0 :(得分:0)

最直接的方法是修改AB的派生解码器,以便在eventType值不正确时它们会失败。这将导致副产品解码器自然找到合适的情况:

import shapeless._
import io.circe.Decoder, io.circe.syntax._
import io.circe.generic.semiauto.deriveDecoder
import io.circe.generic.auto._, io.circe.shapes._

case class A(eventType: String, fieldOne: Int)
case class B(eventType: String, fieldOne: Int, fieldTwo: Int)

type Event = A :+: B :+: CNil

case class X(id: String, events: List[Event])

implicit val decodeA: Decoder[A] = deriveDecoder[A].emap {
  case a @ A("A", _) => Right(a)
  case _ => Left("Invalid eventType")
}

implicit val decodeB: Decoder[B] = deriveDecoder[B].emap {
  case b @ B("B", _, _) => Right(b)
  case _ => Left("Invalid eventType")
}

val doc = """{
  "id" : "id",
  "events" : [
    {
      "eventType" : "B",
      "fieldOne": 1,
      "fieldTwo" : 2
    }
  ]
}"""

然后:

scala> io.circe.jawn.decode[X](doc)
res0: Either[io.circe.Error,X] = Right(X(id,List(Inr(Inl(B(B,1,2))))))

请注意,您仍然可以使用自动派生的编码器-您只需要在解码端进行其他检查即可。 (当然,这是假定您确保不使用无效的事件类型构造AB值,但是由于您询问使用该成员作为鉴别符,所以看起来不错。)

更新:如果您不想枚举解码器,则可以执行以下操作:

import io.circe.generic.decoding.DerivedDecoder

def checkType[A <: Product { def eventType: String }](a: A): Either[String, A] =
  if (a.productPrefix == a.eventType) Right(a) else Left("Invalid eventType")

implicit def decodeSomeX[A <: Product { def eventType: String }](implicit
  decoder: DerivedDecoder[A]
): Decoder[A] = decoder.emap(checkType)

...,它应该与上面的代码完全相同。结构类型的东西运行时成本很小(几乎可以忽略不计),但是它是绝对安全的,在我看来,这是对这些类型进行抽象的一种合理方法。