Как я могу сделать маршаллер для древовидного класса?

Я хочу сделать маршаллер для класса case, поля которого относятся к одному и тому же классу.

case class TreeNode (name: String, parentNode: Option[TreeNode])

если я сделаю сериализатор

implicit val nodeJson = jsonFormat2(TreeNode)

я получаю сообщение об ошибке, что для параметра Option[TreeNode] не найдено имплицитов. Есть ли способ решить эту проблему, кроме написания сериализации с нуля?

PS Еще несколько попыток, которые я сделал с кодировщиком

private def toNode (node: TreeNode): Map[String, Any] = {
    val parent = node.parentNode.map(toNode).orNull
    Map[String, Any] ("name" -> node.name, "parentNode" -> parent)
  }

implicit val treeNodeEncoder: Encoder[TreeNode] =
  Encoder.forProduct2("name", "parentNode")(n =>
    (n.name, n.parentNode.map(toNode).orNull)
  )

это тоже не работает, потому что Map[String, Any] также не имеет неявного


person Dmitry Reutov    schedule 05.10.2020    source источник
comment
При чем тут кошачий эффект? Откуда это Encoder? круг?   -  person Luis Miguel Mejía Suárez    schedule 05.10.2020
comment
@LuisMiguelMejíaSuárez, да io.circe   -  person Dmitry Reutov    schedule 05.10.2020
comment
Является ли иерархия классов sealed? Circe нужна закрытая иерархия, чтобы иметь возможность неявно создавать кодировщики/декодеры.   -  person francoisr    schedule 05.10.2020
comment
Вы не можете получить Encoder[String,Any], но он вам и не нужен. Просто повторно используйте treeNodeEncoder для рекурсивного кодирования parentNode, когда это не null. Но circe должна уметь делать это и из коробки.   -  person francoisr    schedule 05.10.2020
comment
@LuisMiguelMejíaSuárez, мне ничего не нужно, я просто учусь, поэтому любые варианты приветствуются. Большое спасибо за это, пользовательскую кодировку я уже изучил. Таким образом, вы указали, что мне просто не нравится, что пустой parentNode все еще представлен в json как null. Есть ли варианты сделать так, чтобы его не было, просто {"name":"root"} вместо {"name":"root","parentNode":null} ?   -  person Dmitry Reutov    schedule 05.10.2020


Ответы (3)


circe поддерживает это по умолчанию, вам просто нужно использовать полуавтоматический/автоматический механизм получения, предоставляемый библиотекой.
Вы также можете изменить принтер, который хотите использовать для управления идентификацией, и если вы хотите включить или не включить nulls

Взгляните на этот код:

import io.circe.{Decoder, Encoder, Printer, parser}
import io.circe.syntax._
import io.circe.generic.semiauto._

final case class TreeNode(name: String, parentNode: Option[TreeNode] = None)
object TreeNode {
  implicit final val decoder: Decoder[TreeNode] = deriveDecoder
  implicit final val encoder: Encoder[TreeNode] = deriveEncoder
}

val data = TreeNode(name = "child", parentNode = Some(TreeNode(name = "parent")))

val printer = Printer.spaces2SortKeys.copy(dropNullValues = true)
val json = printer.print(data.asJson)
println(json)

println("------------------")

val result = parser.decode[TreeNode](json)
println(result)

Вы можете увидеть, как он работает здесь

person Luis Miguel Mejía Suárez    schedule 05.10.2020

Предполагая, что вы пытаетесь заставить это работать с circe, вот рабочий пример:


import io.circe.{Encoder,Decoder}
import io.circe.generic.semiauto.{deriveEncoder,deriveDecoder}
import io.circe.syntax._

final case class TreeNode (name: String, parentNode: Option[TreeNode])

object TreeNode {
  implicit val encoder: Encoder[TreeNode] = deriveEncoder
  implicit val decoder: Decoder[TreeNode] = deriveDecoder
}

val a = TreeNode("a", None)
val b = TreeNode("b", Some(a))
val c = TreeNode("c", Some(b))

println(c.asJson)

Выход

{
  "name" : "c",
  "parentNode" : {
    "name" : "b",
    "parentNode" : {
      "name" : "a",
      "parentNode" : null
    }
  }
}

Также обратите внимание, что представление деревьев, начиная с листьев, а не с корней, необычно.

person francoisr    schedule 05.10.2020
comment
изображать деревья, начиная с листьев, а не корней, необычно - да, это просто для упрощения примера - person Dmitry Reutov; 05.10.2020

Спасибо всем за помощь, просто чтобы завершить еще один способ, который я обнаружил, как сделать это вручную, если это необходимо

implicit val treeNodeEncoder: Encoder[TreeNode] = new Encoder[TreeNode] {
    def apply(a: TreeNode): Json = {
      val list = List(
        ("name" -> Json.fromString(a.name)),
        ("parentNode" -> a.parentNode.map(this.apply).orNull),
      ).filterNot(_._2 == null)

      Json.obj(list: _*)
    }
  }

Таким образом, мы можем избежать записи parentNode как null и просто пропустить его, если он равен None.

person Dmitry Reutov    schedule 05.10.2020