解析Json时如何解决Circe中的递归解码?

How to resolve recursive decoding in Circe when parsing Json?

我想使用 Circa 解析 JSON 字符串。您可以在下面找到输入 JSON 的示例。

这是一种递归数据。所以我的 属性 entity 包含实体的依赖关系。

我想解析依赖关系以映射 Map[String, Tasks]

{
  "entity": [
    {
      "task_id": "X",
      "type": "test",
      "attributes": {
        "name": "A",
        "random_property_count": 1 // should be ignored
      },
      "dependencies": {
        "random_name_1": {
          "entity": [
            {
              "task_id": "907544AF",
              "type": "test",
              "attributes": {
                "name": "B",
                "random_attribute": "*"
              },
              "dependencies": {
                "random_name_2": {
                  "entity": [
                    {
                      "task_id": "5",
                      "random_prop": "...",  // should be ignored as it's not present in model
                      "type": "test",
                      "attributes": {
                        "name": "C"
                      }
                    }
                  ]
                }
              }
            }
          ]
        }
      }
    }
  ]
}

这是我的代码:

  case class Tasks (entity: Seq[Task])
  case class Task(task_id: String, `type`: String, attributes: Attributes, dependencies: Map[String, Tasks])
  case class Attributes(name: String)

  implicit val decodeTask: Decoder[Task] = deriveDecoder[Task]
  implicit val decodeTasks: Decoder[Tasks] = deriveDecoder[Tasks]
  implicit val decodeAttributes: Decoder[Attributes] = deriveDecoder[Attributes]

  val json = fromInputStream(getClass.getResourceAsStream("/json/example.json")).getLines.mkString
  val tasks = decode[Tasks](json)

  tasks match {
    case Left(failure) => println(failure)
    case Right(json)   => println(json)
  }

当我尝试将 JSON 字符串解析为我的模型时,出现如下错误:

DecodingFailure(Attempt to decode value on failed cursor, List(DownField(dependencies), DownArray, DownField(entity), DownField(random_name_2), DownField(dependencies), DownArray, DownField(entity), DownField(random_name_1), DownField(dependencies), DownArray, DownField(entity)))

可能是什么问题?

DecodingFailure 的第二个成员在这种情况下很有用,因为它提供了失败之前成功操作的历史记录以及失败操作本身(按时间倒序排列,最近的优先)。您可以像这样打印历史记录(或者只是在 DecodingFailure 的字符串表示中检查它):

scala> import io.circe.DecodingFailure
import io.circe.DecodingFailure

scala> io.circe.jawn.decode[Tasks](doc) match {
     |   case Left(DecodingFailure(_, history)) => history.reverse.foreach(println)
     | }
DownField(entity)
DownArray
DownField(dependencies)
DownField(random_name_1)
DownField(entity)
DownArray
DownField(dependencies)
DownField(random_name_2)
DownField(entity)
DownArray
DownField(dependencies)

如果您按照这些步骤进入文档直到最后一个步骤,您将得到以下对象:

{
  "task_id": "5",
  "random_prop": "...",
  "type": "test",
  "attributes": {
    "name": "C"
  }
}

最后一步失败了,它是 DownField(dependencies),这是有道理的,因为该对象没有 dependencies 字段。

有几种方法可以解决此问题。第一个是更改 JSON 表示,以便 entity 数组中的每个对象都有一个 dependencies 字段,即使它只是 "dependencies": {}。如果您不想或不能更改您的 JSON,您可以将 dependencies 成员设为 Option[Map[String, Tasks]](我刚刚确认这特别适用于您的情况)。您还可以定义自定义 Map 解码器,将缺失的字段解码为空映射,但这是一种更具侵入性的方法,我个人不推荐。