scala 中的隐式 json 转换器在运行时为 null

implicit json converter in scala is null at runtime

使用过的播放框架我有 case classes 的文件(在项目中有聚合任务。这很重要)像

case class Project (var tasks: Map[String, Task])
case class Task (var activities: Map[String, String])

并在另一个文件中 trait 用于转换

trait JsonConverters {
  implicit val projectWrite: OWrites[Project] = Json.writes[Project]
  implicit val projectRead: Reads[Project] = Json.reads[Project]
  implicit val taskWrite: OWrites[Task] = Json.writes[Task]
  implicit val taskRead: Reads[Task] = Json.reads[Task]
}
object JsonConverters extends JsonConverters

在我的服务中 class 类似

import support.JsonConverters._
class Service {
  def someMethod(json: String) = {
    val obj = Json.parse(json).as[Map[String, Project]]
  }
}

在编译时一切正常。但是在 json 解析中我在 json 库中得到了 NPE (play.api.libs.json.DefaultReads#mapReads)

implicit def mapReads[K, V](k: String => JsResult[K])(implicit fmtv: Reads[V]): Reads[Map[K, V]] = Reads[Map[K, V]] {

implicit fmtv: Reads[V] 对于任务 class 为空(对于项目一切正常)

我认为这是因为在 .as[Map[String, Project] 中只指定了项目 class,但没有指定任务。但不完全是

我找到了 2 种不同的解决方案来解决这个问题,但对我来说都很难看

1) 在 相同的 文件中声明所有隐式转换器,大小写为 classes。但我想将所有转换器放在单独的文件中。这个解决方案不适合我

2) 在 trait JsonConverters

中使用 lazy mod 为任务声明隐式
trait JsonConverter {
  implicit val projectWrite: OWrites[Project] = Json.writes[Project]
  implicit val projectRead: Reads[Project] = Json.reads[Project]
  implicit lazy val taskWrite: OWrites[Task] = Json.writes[Task]
  implicit lazy val taskRead: Reads[Task] = Json.reads[Task]
}

它工作正常,看起来也不错。但是我不明白为什么在单独的特征中没有惰性它就不能工作?谁能描述一下?或建议其他解决方案

普通变量(val)和惰性变量(lazy val)的初始化顺序不同。

val 按照文件中描述的顺序进行了初始化。
lazy val 在首次访问时 被初始化

在您的情况下,Project 包含一个 Task,因此 projectRead 需要初始化 taskRead 才能完成。

在您的代码中,如果没有 lazyprojectReadtaskRead 之前声明,因此当 projectRead 需要时 taskRead 仍然是 null它。这就是你得到 NPE 的原因。

仍然,编译器可以找到隐式 taskRead 并且不会引发错误

您对 lazy 的修复有效,因为 taskRead 现在在首次访问 时初始化 。现在,taskReadprojectRead 请求时初始化。 NPE 消失了。

您可以通过切换声明顺序来实现相同的修复:

trait JsonConverter {
  implicit val taskWrite: OWrites[Task] = Json.writes[Task]
  implicit val taskRead: Reads[Task] = Json.reads[Task]
  implicit val projectWrite: OWrites[Project] = Json.writes[Project]
  implicit val projectRead: Reads[Project] = Json.reads[Project]
}