【问题标题】:Turn off a "Forward reference" warning in a play framework recursive JSON reader在播放框架递归 JSON 阅读器中关闭“前向引用”警告
【发布时间】:2015-05-09 19:44:42
【问题描述】:

在一个播放框架 (Scala) Web 应用程序中,我解析了一些 JSON 以获得递归类型。为了实现这一点,我使用了读者的惰性引用,就像在play's official documentation 上推荐的那样(向下滚动到“递归类型”)。

这可行,但我收到警告:

[info] Compiling 1 Scala source to /path-to-project/target/scala-2.11/classes...
[warn] /path-to-project/app/controllers/JSONFormats.scala:116: Reference to uninitialized value layerTreeNodeFormat
[warn]   val folderNodeFormat = Format(folderNodeReads, Json.writes[FolderNode])
[warn]                                                             ^
[warn] one warning found

有什么办法可以关闭此警告吗?我查看了 Scala 的 @unchecked,但我不确定如何(如果有的话)在这里应用它。

谢谢!


编辑:以下是代码的相关部分。 JSON 解析器解析地图层的异构树。一些节点(“层”)是叶子,而其他节点(“文件夹”)可以包含层和其他文件夹(因此递归)。在 Scala 方面,有一个抽象基类 LayerTreeNode 和两个具体案例类:LayerNodeFolderNode

object ProjectJSONFormats {

  // omitted code...

  /** Turns the tuple parsed from JSON into a FolderNode. */
  def tupleToFolder( id:String, jsType:String, name:String, children:Seq[LayerTreeNode] ) = FolderNode(id, name, children)

  val folderNodeReads: Reads[FolderNode] = (
    (JsPath \ "id").read[String] and
      (JsPath \ "type").read[String] and
      (JsPath \ "title").readNullable[String].map( _.getOrElse("") ) and
      (JsPath \ "children").lazyReadNullable(Reads.seq[LayerTreeNode](layerTreeNodeReads)).map( _.getOrElse(Seq[LayerTreeNode]()))
    )( tupleToFolder _)

  val folderNodeFormat = Format(folderNodeReads, Json.writes[FolderNode])

  implicit val layerNodeFormat = Json.format[LayerNode]

  val layerTreeNodeReads: Reads[LayerTreeNode] = new Reads[LayerTreeNode] {
    override def reads(json: JsValue): JsResult[LayerTreeNode] = {
      if ( (json\"type").as[String] == "folder" ) {
        folderNodeFormat.reads(json)
      } else {
        layerNodeFormat.reads(json)
      }
    }
  }
  val layerTreeNodeWrites: Writes[LayerTreeNode] = new Writes[LayerTreeNode] {
    override def writes(o: LayerTreeNode): JsValue = o match {
      case f:FolderNode => folderNodeFormat.writes(f)
      case l:LayerNode  => layerNodeFormat.writes(l)
    }
  }
  implicit val layerTreeNodeFormat:Format[LayerTreeNode] = Format( layerTreeNodeReads, layerTreeNodeWrites )

}

【问题讨论】:

  • @unchecked 不适用于此处,因为它用于忽略类型检查器警告。你确定这个警告没有任何价值吗?初始化顺序可能最终会在这里咬你,但如果没有看到实际代码就很难说。
  • 谢谢。正如我所说 - 这是惯用的。它不会咬我,因为我将它作为参数传递给按名称调用的方法 JsPath#lazyReadNullable。 (playframework.com/documentation/2.3.x/api/scala/…)
  • 您能否将实际代码添加到您的问题中?

标签: scala playframework


【解决方案1】:

编译器完全证明了该警告是合理的,所以不要试图忽略它。你没有分享你的案例类结构,所以我不得不做一些猜测,但问题还是会出现。 Reads 可能是安全的,但 Writes 不是。

sealed abstract class LayerTreeNode(id: String, name: String)
case class FolderNode(id: String, name: String, children: Seq[LayerTreeNode]) extends LayerTreeNode(id, name)
case class LayerNode(id: String, name: String) extends LayerTreeNode(id, name)

val folder = FolderNode("ABC", "Parent", Seq(LayerNode("DEF", "Child")))

import ProjectJSONFormats._

scala> Json.toJson(folder)
java.lang.NullPointerException
  at play.api.libs.json.Json$.toJson(Json.scala:108)
  at play.api.libs.json.DefaultWrites$$anon$3$$anonfun$writes$2.apply(Writes.sc
  ... 43 elided

发生了什么?正如编译器警告我们的那样,Json.writes[FolderNode] 需要一个隐式的Writes[LayerNode]Format[LayerNode]。但是layerNodeFormat 是在Json.writes[FolderNode] 调用之后定义的,这意味着我们可以看到它,但它是未初始化的。现在 folderNodeFormatwrites 方法有一个 NPE 等待在最糟糕的时刻出现。

修复很简单,只需让您的 Writes 也变得懒惰。即:

lazy val folderNodeFormat = Format(folderNodeReads, Json.writes[FolderNode])

lazy val layerTreeNodeWrites: Writes[LayerTreeNode] = ...

它有效:

scala> Json.toJson(folder)
res10: play.api.libs.json.JsValue = {"id":"ABC","name":"Parent","children":[{"id":"DEF","name":"Child"}]}

一般的问题是初始化顺序,如果你不注意,它肯定会咬你。我在this answer 中写了更多关于它的内容。这同样适用于ReadsWrites


附带说明,使用(json \ "type").as[String] == "folder") 也是不安全的。如果"type" 实际上不是String,则会抛出异常。

val js = Json.parse("""{
  "id": "ABC",
  "name": "parent",
  "type": 1,
  "children": [
    {"id": "DEF", "name": "child", "type": "leaf"}
  ]
}""")

scala> js.validate[LayerTreeNode]
play.api.libs.json.JsResultException: JsResultException(errors:List((,List(ValidationError(error.expected.jsstring,WrappedArray())))))
  ... 43 elided // We probably don't want this to happen!

最好使用validateflatMap

  val layerTreeNodeReads: Reads[LayerTreeNode] = new Reads[LayerTreeNode] {
    override def reads(json: JsValue): JsResult[LayerTreeNode] = {
      (json \ "type").validate[String] flatMap {
        case "folder" => folderNodeFormat.reads(json)
        case _ => layerNodeFormat.reads(json)
      }
    }
  }

现在如果我们validate[LayerTreeNode] 可能发生的最糟糕的事情是我们得到一个JsError 而不是抛出异常。

scala> js.validate[LayerTreeNode]
res18: play.api.libs.json.JsResult[LayerTreeNode] = JsError(List((,List(ValidationError(error.expected.jsstring,WrappedArray())))))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-05-03
    • 2016-03-07
    • 2019-02-02
    • 1970-01-01
    • 2021-01-29
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多