简体   繁体   中英

How to deserialize a scala tree with JSON4S

Serialization works fine but I have nothing for deserialization. I found interesting solution for abstract class here How to serialize sealed abstract class with Json4s in Scala? but it doesn't deal with trees.

This the code of my test with a standard JSON4S :

import org.json4s._
import org.json4s.native.JsonMethods._
import org.json4s.native.Serialization.{ read, write }
import org.json4s.native.Serialization

abstract class Tree
case class Node(nameN: String, trees: List[Tree]) extends Tree
case class Leaf(nameL: String) extends Tree

object Tree extends App {
  implicit val formats = Serialization.formats(NoTypeHints)

  // object creation to test the serialization
  val root =
    Node(
      "Grand Pavois project",
      List(
        Node(
          "studies",
          List(
            Leaf("preliminary studies"),
            Leaf("detailled studies")
          )
        ),
        Node(
          "realization",
          List(
            Leaf("ground"),
            Leaf("building"),
            Leaf("roof")
          )
        ),
        Node(
          "delivery",
          List(
            Leaf("quality inspection"),
            Leaf("customer delivery")
          )
        )
      )
    )

  val serialized = write(root) // object creation and serialization
  println(s"serialized: $serialized") // print the result, this is OK

  // and now what about deserialization?
  // string creation for deserialization
  // ( it is the same as serialized above, I do like that to trace for the demo)
  val rootString = """
{
  "nameN": "Grand Pavois project",
  "trees": [
    {
      "nameN": "studies",
      "trees": [
        {
          "nameL": "preliminary studies"
        },
        {
          "nameL": "detailled studies"
        }
      ]
    },
    {
      "nameN": "realization",
      "trees": [
        {
          "nameL": "ground"
        },
        {
          "nameL": "building"
        },
        {
          "nameL": "roof"
        }
      ]
    },
    {
      "nameN": "delivery",
      "trees": [
        {
          "nameL": "quality inspection"
        },
        {
          "nameL": "customer delivery"
        }
      ]
    }
  ]
}
"""
//standard deserialization below that produce an error :
// "Parsed JSON values do not match with class constructor"
val rootFromString = read[Tree](rootString)
}

Now I guess the solution is with a custom deserializer probably a recusive one but how to define it? That is the question. Thanks for your help.

This solution doesn't use a custom deserializer, but instead creates a type that matches both Node and Leaf and then converts to the appropriate type later.

case class JsTree(nameN: Option[String], nameL: Option[String], trees: Option[List[JsTree]])

def toTree(node: JsTree): Tree = node match {
  case JsTree(Some(name), None, Some(trees)) =>
    Node(name, trees.map(toTree))
  case JsTree(None, Some(name), None) =>
    Leaf(name)
  case _ =>
    throw new IllegalArgumentException
}

val rootFromString = toTree(read[JsTree](rootString))

The JsTree class will match both Node and Leaf values because it has option fields that match all the fields in both classes. The toTree method recursively converts the JsTree to the appropriate Tree subclass based on which fields are actually present.

Update: Custom serializer

Here is the solution using a custom serializer:

import org.json4s.JsonDSL._

class TreeSerializer extends CustomSerializer[Tree](format => ({
  case obj: JObject =>
    implicit val formats: Formats = format

    if ((obj \ "trees") == JNothing) {
      Leaf(
        (obj \ "nameL").extract[String]
      )
    } else {
      Node(
        (obj \ "nameN").extract[String],
        (obj \ "trees").extract[List[Tree]]
      )
    }
}, {
  case node: Node =>
    JObject("nameN" -> JString(node.nameN), "trees" -> node.trees.map(Extraction.decompose))
  case leaf: Leaf =>
    "nameL" -> leaf.nameL
}))

Use it like this:

implicit val formats: Formats = DefaultFormats + new TreeSerializer

read[Tree](rootString)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM