在播放框架递归 JSON reader 中关闭 "Forward reference" 警告
Turn off a "Forward reference" warning in a play framework recursive JSON reader
在播放框架 (Scala) 网络应用程序上,我解析了一些 JSON 获得递归类型。为此,我使用 reader 的惰性引用,就像 play's official documentation 上推荐的那样(向下滚动到 "recursive types")。
这有效,但我收到警告:
[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 解析器解析一个异构的地图层树。一些节点 ("layers") 是叶子,而其他节点 ("folders") 可以包含图层和其他文件夹(因此递归)。在 Scala 方面,有一个抽象基础 class、LayerTreeNode
和两个具体案例 class:LayerNode
和 FolderNode
。
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 )
}
警告是编译器完全合理的,所以不要试图忽略它。你没有分享你的 case class 结构,所以我不得不做一些猜测,但无论如何问题都会出现。 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$$anonfun$writes.apply(Writes.sc
... 43 elided
发生了什么事?正如编译器警告我们的那样,Json.writes[FolderNode]
需要隐式的 Writes[LayerNode]
或 Format[LayerNode]
。但是layerNodeFormat
是在调用Json.writes[FolderNode]
之后定义的,这意味着我们可以看到它,但是它是未初始化的。现在 folderNodeFormat
的 writes
方法有一个 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 中写了更多关于它的内容。这同样适用于 Reads
和 Writes
。
附带说明一下,使用 (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!
最好用validate
和flatMap
:
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())))))
在播放框架 (Scala) 网络应用程序上,我解析了一些 JSON 获得递归类型。为此,我使用 reader 的惰性引用,就像 play's official documentation 上推荐的那样(向下滚动到 "recursive types")。
这有效,但我收到警告:
[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 解析器解析一个异构的地图层树。一些节点 ("layers") 是叶子,而其他节点 ("folders") 可以包含图层和其他文件夹(因此递归)。在 Scala 方面,有一个抽象基础 class、LayerTreeNode
和两个具体案例 class:LayerNode
和 FolderNode
。
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 )
}
警告是编译器完全合理的,所以不要试图忽略它。你没有分享你的 case class 结构,所以我不得不做一些猜测,但无论如何问题都会出现。 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$$anonfun$writes.apply(Writes.sc
... 43 elided
发生了什么事?正如编译器警告我们的那样,Json.writes[FolderNode]
需要隐式的 Writes[LayerNode]
或 Format[LayerNode]
。但是layerNodeFormat
是在调用Json.writes[FolderNode]
之后定义的,这意味着我们可以看到它,但是它是未初始化的。现在 folderNodeFormat
的 writes
方法有一个 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 中写了更多关于它的内容。这同样适用于 Reads
和 Writes
。
附带说明一下,使用 (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!
最好用validate
和flatMap
:
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())))))