Khorkhe
Khorkhe

Reputation: 1126

Converting "recursive" object to JSON (Play Framework 2.4 with Scala)

I have reached a point where my code compiles successfully, yet I have doubts about my solution and am posting this question for that reason.

I have a Node class defined as:

case class Node(id: Long, label: String, parent_id: Option[Long])

The reason I quote/unquote recursive is because technically, I do not store a Node within a Node. Rather, each node has a pointer to its parent and I can say: give me all children of Node id=X.

Here is an example tree, for the sake of visualization. I would like to give the root_node's ID, and obtain the tree's conversion to a Json String:

root_node
|_ node_1
|  |_ node_11
|     |_ node_111
|_ node_2
|_ node_3

The Json would look like:

{"title": "root_node", "children": [...]}

with the children array containing node_1, 2 and 3 etc... recursively

Here is the Writes Converter for Node:

/** json converter of Node to JSON */
implicit val NodeWrites = new Writes[Node] {
  def writes(node: Node) = Json.obj(
    "title"  -> node.label,
    "children" -> Node.getChildrenOf(node.id)
  )
}

Quoting Play docs:

The Play JSON API provides implicit Writes for most basic types, such as Int, Double, String, and Boolean. It also supports Writes for collections of any type T that a Writes[T] exists.

I need to point out that Node.getChildrenOf(node.id) returns a List of Nodes from the DB. So according to Play's docs, I should be able to convert a List[Node] to Json. It seems that doing this within the Writes converter itself is a bit more troublesome.

Here is the resulting error from running this code:

type mismatch;
 found   : List[models.Node]
 required: play.api.libs.json.Json.JsValueWrapper
 Note: implicit value NodeWrites is not applicable here because it comes after the application point and it lacks an explicit result type

I added the "explicit result type" to my Writes converter, here is the result:

/** json converter of Node to JSON */
implicit val NodeWrites: Writes[Node] = new Writes[Node] {
  def writes(node: Node) = Json.obj(
    "title"  -> node.label,
    "children" -> Node.getChildrenOf(node.id)
  )
}

The code now executes properly, I can visualize the tree on the browser.

Even though this looks to me like the cleanest working solution, IntelliJ still complains about the line:

"children" -> Node.getChildrenOf(node.id)

saying:

Type mismatch: found(String, List[Node]), required (String, Json.JsValueWrapper)

Could it be that IntelliJ's error reporting is not based off of the Scala compiler?

Finally, is the overall approach of the JSON converter terrible?

Thanks and sorry for the long post.

Upvotes: 0

Views: 626

Answers (1)

irundaia
irundaia

Reputation: 1730

The problem lies in "children" -> Node.getChildrenOf(node.id). Node.getChildrenOf(node.id) returns a List[Node]. Whereas any attribute in the Json.obj expects JsValueWrappers. In this case a JsArray.

Something like this should work:

implicit val writes = new Writes[Node] {
  def writes(node: Node) = Json.obj(
    "title" -> node.label, 
    // Note that we have to pass this since the writes hasn't been defined just yet.
    "children" -> JsArray(Node.getChildrenOf(node).map(child => Json.toJson(child)(this)))
  )
}

This at least compiles, I haven't tested it with any data though.

Upvotes: 1

Related Questions