【问题标题】:How can i make marshaller for tree-like case class?我怎样才能为树状案例类制作编组器?
【发布时间】:2020-10-05 23:59:29
【问题描述】:

我想为具有引用同一类的字段的案例类制作编组器。

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] 也没有隐式

【问题讨论】:

  • cats-effect 与此有什么关系? Encoder 来自哪里? ?
  • @LuisMiguelMejíaSuárez,是的 io.circe
  • 类层次结构是sealed吗? Circe 需要密封的层次结构才能隐式创建编码器/解码器。
  • 您无法获得Encoder[String,Any],但您也不需要它。当它不是null 时,只需重用treeNodeEncoder 以递归编码parentNode。但是 circe 也应该能够开箱即用地做到这一点。
  • @LuisMiguelMejíaSuárez,我不需要任何东西,我只是学习,所以欢迎任何选择。非常感谢这个,我已经学会了自定义编码。你提供的这种方式我只是不喜欢那个空的 parentNode 仍然在 json 中显示为 null。是否有任何选项可以让它缺席,只是 {"name":"root"} 而不是 {"name":"root","parentNode":null}

标签: scala marshalling circe


【解决方案1】:

假设您正在尝试使用 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
    }
  }
}

另请注意,从叶子而不是根开始表示树木是不寻常的。

【讨论】:

  • “表示从叶子而不是根开始的树是不寻常的” - 是的,这只是为了简化示例
【解决方案2】:

circe 开箱即用,您只需要使用库提供的semiautomatic / automatic derivation 机制即可。
您还可以修改要用于控制标识的打印机以及是否要包含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)

你可以看到它在运行here

【讨论】:

    【解决方案3】:

    感谢大家的帮助,只是为了让它完成我发现的另一种方式,如果需要,如何手动完成

    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就跳过它

    【讨论】:

      猜你喜欢
      • 2012-09-21
      • 2022-01-17
      • 2019-09-04
      • 1970-01-01
      • 2021-10-10
      • 1970-01-01
      • 2017-02-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多