【问题标题】:Flattening nested JSON objects with Circe使用 Circe 展平嵌套的 JSON 对象
【发布时间】:2020-01-21 09:11:25
【问题描述】:

假设我有一个这样的 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 channel 的另一个常见问题解答。)

【问题讨论】:

    标签: json scala circe


    【解决方案1】:

    您可以使用递归方法在 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 值,如果它是非 JSON 对象值,则返回 None,作为该字段不需要展平的信号,或者返回包含以下序列的 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 对象会溢出堆栈,但可能直到您达到数千级深度时才会溢出,因此在实践中不太可能成为问题。您可以在没有太多麻烦的情况下使其尾递归,但对于只有几层嵌套的常见情况会产生额外的开销。

    【讨论】:

      【解决方案2】:

      我提出了solution by Travis Brown 的变体。变体涉及 JSON 列表中的对象,即如何处理

      {
        "foo": true,
        "bar": {
          "baz": 1,
          "qux": {
            "msg": "hello world",
            "wow": [{"x": 1, "y": 2}, {"x": 3, "y": 4}]
          }
        }
      }
      

      递归处理列表中的对象的一种可能的解决方案是以下实现,其中列表中的位置被视为附加的关键部分

      def flattenDeep(combineKeys: (String, String) => String)(value: Json): Json = {
        def flattenToFields(value: Json): Option[Iterable[(String, Json)]] = {
          value.fold(
            jsonNull = None,
            jsonNumber = _ => None,
            jsonString = _ => None,
            jsonBoolean = _ => None,
            jsonObject = { obj =>
              val fields = obj.toIterable.flatMap {
                case (field, json) =>
                  flattenToFields(json).fold(Iterable(field -> json)) {
                    _.map {
                      case (innerField, innerJson) =>
                        combineKeys(field, innerField) -> innerJson
                    }
                  }
              }
              Some(fields)
            },
            jsonArray = { array =>
              val fields = array.zipWithIndex.flatMap {
                case (json, index) =>
                  flattenToFields(json).fold(Iterable(index.toString -> json)) {
                    _.map {
                      case (innerField, innerJson) =>
                        combineKeys(index.toString, innerField) -> innerJson
                    }
                  }
              }
              Some(fields)
            }
          )
        }
        flattenToFields(value).fold(value)(Json.fromFields)
      }
      

      通过这个实现,上面的例子被扁平化为:

      {
        "foo" : true,
        "bar_baz" : 1,
        "bar_qux_msg" : "hello world",
        "bar_qux_wow_0_x" : 1,
        "bar_qux_wow_0_y" : 2,
        "bar_qux_wow_1_x" : 3,
        "bar_qux_wow_1_y" : 4
      }
      

      对于更深的嵌套结构,仍然可以得到一个平面表示,例如

      {
        "foo": true,
        "bar": {
          "baz": 1,
          "qux": {
            "msg": "hello world",
            "wow": [
              {
                "x": 1,
                "y": 2
              },
              {
                "x": 3,
                "y": 4
              }
            ],
            "deeper": [
              {
                "alpha": {
                  "h": 12,
                  "m": 1
                },
                "beta": [ "a", "b", "c" ]
              },
              {
                "alpha": {
                  "h": 21,
                  "m": 0
                },
                "beta": [ "z" ]
              }
            ]
          }
        }
      }
      

      会被压扁成

      {
        "foo" : true,
        "bar_baz" : 1,
        "bar_qux_msg" : "hello world",
        "bar_qux_wow_0_x" : 1,
        "bar_qux_wow_0_y" : 2,
        "bar_qux_wow_1_x" : 3,
        "bar_qux_wow_1_y" : 4,
        "bar_qux_deeper_0_alpha_h" : 12,
        "bar_qux_deeper_0_alpha_m" : 1,
        "bar_qux_deeper_0_beta_0" : "a",
        "bar_qux_deeper_0_beta_1" : "b",
        "bar_qux_deeper_0_beta_2" : "c",
        "bar_qux_deeper_1_alpha_h" : 21,
        "bar_qux_deeper_1_alpha_m" : 0,
        "bar_qux_deeper_1_beta_0" : "z"
      }
      

      【讨论】:

        猜你喜欢
        • 2021-10-19
        • 2012-05-29
        • 2019-01-26
        • 2020-03-02
        • 1970-01-01
        • 2019-01-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多