问题
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.
回答1:
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)
来源:https://stackoverflow.com/questions/54322448/how-to-deserialize-a-scala-tree-with-json4s