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

时间:2019-01-18 10:08:47

标签: json scala playframework

使用过的游戏框架,我的文件带有 case类(在Project中具有聚合任务。这一点很重要),例如

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

在我的服务类别中,类似

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] 为空(对于Task类)(对于Project全部正常)

我认为是因为在.as[Map[String, Project]中仅指定了Project类,而不指定了Task。但这不完全是

我为此问题找到了2种不同的解决方案,但这两种方法对我来说都是丑陋的

1)在具有案例类的相同文件中声明所有隐式转换器。但我想将所有转换器都放在单独的文件中。此解决方案不适合我

2)在trait JsonConverters

中使用 lazy mod声明对Task的隐含
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]
}

它工作正常,并且看起来不错。但是我不理解,为什么在单独特征中没有懒惰不能正常工作?有人可以描述吗?或建议其他解决方案

1 个答案:

答案 0 :(得分:2)

琐碎变量(val)和惰性变量(lazy val)的初始化顺序是不同的。

val的初始化顺序是文件中描述的顺序
lazy val首次访问时被初始化

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

在您的代码中,没有lazy的情况下,projectReadtaskRead之前声明,因此taskReadnull需要时仍projectRead。这就是为什么您要使用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]
}