将 Json 读入 Scalaz 树

read Json into Scalaz Tree

这里是 Scala 新手。

我使用Play 提供了一个json API 用于读写类似目录的结构。因此我使用Scalaz.Tree,它提供了遍历、更新和重建树的方法。

将树格式化为 json 效果很好:

case class File(id: String = BSONObjectID.generate.toString(), name: String, noteBookId: String = null)
implicit val fileFormat: Format[File] = Json.format[File]

implicit def treeWrites: Writes[Tree[File]] =
new Writes[Tree[File]] {
  def writes(o: Tree[File]) = o match {
    case Node(file, children) => Json.obj(
      "name" -> file.name,
      "id" -> file.id,
      "children" -> JsArray(children.map(Json.toJson(_))),
      "notebookId" -> file.noteBookId
    )
  }
}

但是,将 json 读入树中失败了

implicit def treeReads: Reads[Tree[File]] = (
  //(__ \ "children").lazyRead(Reads.seq[File](treeReads)) and
  (__ \ "children").read[Tree[File]] and
  (__ \ "name").read[String] and 
  (__ \ "notebookid").read[String] and // <-- this is line 41, where the error message points at!!
  (__ \ "id").read[String]
)(apply _)

implicit val treeFormat: Format[Tree[File]] = Format(treeReads, treeWrites)

我得到的错误:

[error] /home/dikken/Development/core-service-spaas/app/models/dirTree.scala:41: overloaded method value apply with alternatives:
[error]   [B](f: B => (scalaz.Tree[model.treedir.File], String, String, String))(implicit fu: play.api.libs.functional.ContravariantFunctor[play.api.libs.json.Reads])play.api.libs.json.Reads[B] <and>
[error]   [B](f: (scalaz.Tree[model.treedir.File], String, String, String) => B)(implicit fu: play.api.libs.functional.Functor[play.api.libs.json.Reads])play.api.libs.json.Reads[B]
[error]  cannot be applied to ((=> Nothing) => scalaz.Tree[Nothing])
[error]     (__ \ "id").read[String] and
[error]                              ^
[error] one error found
[error] (compile:compile) Compilation failed

这是否意味着我必须在有 Tree of Nothing 的情况下进行模式匹配?我应该怎么做才好?

感谢任何帮助!发送!

我假设 apply _ 实际上是 File.apply _,这在这里不起作用。 File.apply接受caseclassFile的参数(其中有3个)。使用 JSON 组合器,它试图将上述四个参数传递给 File.apply,这不会混合。它不产生Tree[File]。您需要做的是将 File.apply 替换为接受 (children, notebookid, name, id) 作为参数的方法,并生成 Tree[File].

这是一个有点粗糙的方法:

def jsonToTree(children: Seq[Tree[File]], name: String, notebookid: String, id: String): Tree[File] =
    Tree.node(File(id, name, notebookid), children.toStream)

Reads 现在看起来更像是这样:

implicit def treeReads: Reads[Tree[File]] = (
  (__ \ "children").lazyRead[Seq[Tree[File]]](Reads.seq(treeReads)).orElse(Reads.pure(Nil)) and
  (__ \ "name").read[String] and 
  (__ \ "notebookid").read[String] and
  (__ \ "id").read[String]
)(jsonToTree _)

你也更接近注释掉的行。因为这是一个递归结构,所以我们需要使用lazyRead.

测试:

val js = Json.parse("""{
    "id": "1",
    "name": "test",
    "notebookid": "abc",
    "children": [
        {
            "id": "2",
            "name": "test222",
            "notebookid": "ijk"
        },
        {
            "id": "3",
            "name": "test333",
            "notebookid": "xyz"
        }
    ]
}""")

scala> val tree = js.as[Tree[File]]
tree: scalaz.Tree[File] = <tree>

scala> tree.rootLabel
res8: File = File(1,test,abc)

scala> tree.subForest
res9: Stream[scalaz.Tree[File]] = Stream(<tree>, ?)

这也可以在没有组合器的情况下完成(当然以不同的方式)(前提是有一个隐式 Reads[File] 可用):

implicit def treeReads: Reads[Tree[File]] = new Reads[Tree[File]] {
    def reads(js: JsValue): JsResult[Tree[File]] = {
        js.validate[File] map { case file =>
            (js \ "children").validate[Stream[Tree[File]]].fold(
                _        => Tree.leaf(file),
                children => Tree.node(file, children)
            )
        } 
    }
}