Как десериализовать дерево скала с помощью JSON4S - PullRequest
0 голосов
/ 23 января 2019

Сериализация работает нормально, но у меня нет ничего для десериализации.Я нашел интересное решение для абстрактного класса здесь Как сериализовать запечатанный абстрактный класс с Json4s в 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)
}

Теперь я полагаю, что решение заключается в использовании десериализатора, возможно рекурсивного, но как его определить?Вот в чем вопрос.Спасибо за вашу помощь.

1 Ответ

0 голосов
/ 23 января 2019

Это решение не использует пользовательский десериализатор, а вместо этого создает тип, который совпадает с Node и Leaf, а затем преобразует в соответствующий тип позже.

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 будет соответствовать значениям Node и Leaf, поскольку он имеет поля параметров, соответствующие всем полям в обоих классах.Метод 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)
...