【问题标题】:How to deserialize a scala tree with JSON4S如何使用 JSON4S 反序列化 Scala 树
【发布时间】:2019-01-23 07:59:08
【问题描述】:

序列化工作正常,但我没有反序列化。我在这里找到了抽象类的有趣解决方案How to serialize sealed abstract class with Json4s in Scala?,但它不处理树。

这是我使用标准 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)
}

现在我想解决方案是使用自定义反序列化器,可能是递归的,但如何定义它?就是那个问题。感谢您的帮助。

【问题讨论】:

    标签: scala data-structures deserialization json-deserialization json4s


    【解决方案1】:

    此解决方案不使用自定义反序列化器,而是创建一个匹配 NodeLeaf 的类型,然后稍后转换为适当的类型。

    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))
    

    JsTree 类将匹配 NodeLeaf 值,因为它具有匹配两个类中所有字段的选项字段。 toTree 方法根据实际存在的字段递归地将 JsTree 转换为适当的 Tree 子类。

    更新:自定义序列化程序

    这是使用自定义序列化程序的解决方案:

    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
    }))
    

    像这样使用它:

    implicit val formats: Formats = DefaultFormats + new TreeSerializer
    
    read[Tree](rootString)
    

    【讨论】: