首页 > 解决方案 > 使用 Circe 展平嵌套的 JSON 对象

问题描述

假设我有一个这样的 JSON 对象:

{
   "foo": true,
   "bar": {
      "baz": 1,
      "qux": {
        "msg": "hello world",
        "wow": [null]
      }
   }
}

我想递归地将其展平为单层,键与下划线合并:

{
   "foo": true,
   "bar_baz": 1,
   "baz_qux_msg": "hello world",
   "baz_qux_wow": [null]
}

我怎么能用Circe做到这一点?

(注意:这是来自Circe Gitter 频道的另一个常见问题解答。)

标签: jsonscalacirce

解决方案


您可以使用递归方法在 Circe 中轻松做到这一点:

import io.circe.Json

def flatten(combineKeys: (String, String) => String)(value: Json): Json = {
  def flattenToFields(value: Json): Option[Iterable[(String, Json)]] =
    value.asObject.map(
      _.toIterable.flatMap {
        case (k, v) => flattenToFields(v) match {
          case None => List(k -> v)
          case Some(fields) => fields.map {
            case (innerK, innerV) => combineKeys(k, innerK) -> innerV
          }
        }
      }
    )

  flattenToFields(value).fold(value)(Json.fromFields)
}

在这里,我们的内部flattenToFields方法获取每个 JSON 值,None如果它是非 JSON 对象值,则返回,作为该字段不需要展平的信号,或者Some在 JSON 对象的情况下包含一系列展平字段。

如果我们有这样的 JSON 值:

val Right(doc) = io.circe.jawn.parse("""{
   "foo": true,
   "bar": {
      "baz": 1,
      "qux": {
        "msg": "hello world",
        "wow": [null]
      }
   }
}""")

我们可以像这样验证它flatten是否符合我们的要求:

scala> flatten(_ + "_" + _)(doc)
res1: io.circe.Json =
{
  "foo" : true,
  "bar_baz" : 1,
  "bar_qux_msg" : "hello world",
  "bar_qux_wow" : [
    null
  ]
}

请注意,这flattenToFields不是尾递归,并且会溢出深度嵌套的 JSON 对象的堆栈,但可能直到你有几千层深,所以在实践中不太可能成为问题。您可以在没有太多麻烦的情况下使其尾递归,但是对于只有几层嵌套的常见情况会产生额外的开销。


推荐阅读