Play Framework 2.8.2 - 未找到类型(子类)的 Json 序列化程序

Play Framework 2.8.2 - No Json serializer found for type (subclass)

我正在将我的一些服务从 Play 2.7.x 迁移到最新的 2.8.2,以及 scala 2.13.2 和 sbt 1.3.12。

不过我遇到了游戏障碍-json,Reads[A]

我有以下设置:

sealed trait Charge {}
case class ChargeOne(one: Int) extends Charge
case class ChargeTwo(two: Int) extends Charge

object Charge {
  implicit val writes: Writes[Charge] = (charge: Charge) => {...}
}

我们有一些测试看起来像这样

val chargeOne = ChargeOne(1)
val json = Json.toJson(charge)

json mustBe "..."

并且这些在 play 2.7.x 和 scala 2.12.x 中工作正常,它也适用于 scala 2.13.2,但是在将 play 升级到 2.8.2 之后,以下编译时出现错误:

No Json serializer found for type ChargeOne. Try to implement an implicit Writes or Format for this type.

而且我必须将 .asInstanceOf[Charge] 添加到我的测试中才能使其正常工作。

这里发生了什么?是play-json吗?还是scala?有谁知道如何“修复”它?

如错误消息中所示,需要 ChargeOneWrites(或 Format)实例,而代码仅提供 Reads

import play.api.libs.json._ // BTW Recommend to provide import in code examples

sealed trait Charge {}
case class ChargeOne(one: Int) extends Charge
case class ChargeTwo(two: Int) extends Charge

object Charge {
  implicit val format: OFormat[Charge] = {
    // Need to define instance for the subtypes (no auto-materialization)
    implicit def one = Json.format[ChargeOne]
    implicit def two = Json.format[ChargeTwo]

    Json.format[Charge]
  }
}

然后你可以看到 WritesReads 是不变的(类型类)所以只解析父类型 Charge:

scala> Json.toJson(ChargeOne(1))
<console>:17: error: No Json serializer found for type ChargeOne. Try to implement an implicit Writes or Format for this type.
       Json.toJson(ChargeOne(1))
                  ^

scala> Json.toJson(ChargeOne(1): Charge)
res1: play.api.libs.json.JsValue = {"one":1,"_type":"ChargeOne"}

如果您不想在每个 toJson 上添加注释:

import play.api.libs.json._ // BTW Recommend to provide import in code examples

sealed trait Charge {}
case class ChargeOne(one: Int) extends Charge
case class ChargeTwo(two: Int) extends Charge

object Charge {
  implicit val format: OFormat[Charge] = {
    // Need to define instance for the subtypes (no auto-materialization)
    implicit def one = Json.format[ChargeOne]
    implicit def two = Json.format[ChargeTwo]

    Json.format[Charge]
  }

  implicit def genericWrites[T <: Charge]: OWrites[T] =
    format.contramap[T](c => c: Charge)

  implicit def genericReads[T <: Charge](implicit evidence: scala.reflect.ClassTag[T]): Reads[T] = format.collect[T](JsonValidationError(s"Type mismatch: ${evidence.runtimeClass.getName}")) {
    case `evidence`(t) => t
  }
}
    
scala> Json.toJson(ChargeOne(1))
res0: play.api.libs.json.JsValue = {"one":1,"_type":"ChargeOne"}

scala> Json.toJson(ChargeOne(1)).validate[Charge]
res0: play.api.libs.json.JsResult[Charge] = JsSuccess(ChargeOne(1),)

注意#1:重要的是要看到(反)序列化 ChargeOne(或任何子类型)作为 Charge(或任何类似的父类型) to/from JSON 与“直接”执行不同。 JSON 表示与密封家族序列化不同,需要鉴别器 JSON 字段(参见 _type)。

scala> Json.writes[ChargeOne].writes(ChargeOne(1))
res1: play.api.libs.json.JsObject = {"one":1}

scala> Json.toJson(ChargeOne(1)) // .. as Charge parent type
res2: play.api.libs.json.JsValue = {"one":1,"_type":"ChargeOne"}

注意#2:如果一些类似的用例在以前的版本中错误地工作,它会导致难以预测的 JSON 表示,以及关键的隐式解析很多情况下,这就是这种行为的原因 change/fix(参见 pull request)。