使用 jq 将嵌套的 JSON 文件分解为具有唯一键的平面列表

Posted

技术标签:

【中文标题】使用 jq 将嵌套的 JSON 文件分解为具有唯一键的平面列表【英文标题】:Decompose nested JSON file to flat list with unique keys, using jq 【发布时间】:2017-12-26 23:19:25 【问题描述】:

给定一个 JSON 文件的格式:


    "key00" : 
        "key10" : 
            "20170405" : 
                "val0" : 10,
                ...
                "valn" : 12
            , 
            "20170404" : 
                "val0" : 5,
                ...
                "valn" : 43
            ,
            ...
         ,
         "key11" : ...,
         ...
     ,
     "key01" : ...,
     "key02" : ...,
     ...

我想使用jq 将树分解为一个扁平列表,其格式如下所示。此过程应选择层次结构中的一个特定键,即日期,并且对于树中该日期的每个实例,合并该日期的值,同时根据值在树中的位置使它们的键唯一:

[
    
        "date" : "20170405",
        "key00.key10.val0" : 10,
        ...
        "key00.key10.valn" : 12
    ,
    
        "date" : "20170404",
        "key00.key10.val0" : 10,
        ...
        "key00.key10.valn" : 12
    ,
    ...
    
        "date" : "20170403",
        "key0n.key1n.val0" : 10,
        ...
        "key0n.key1n.valn" : 12
    ,
]

知道嵌套结构,假设它是刚性的,我在 Perl 中使用一组 for 循环来执行此操作。但如果结构发生变化,程序就会中断。此外,对于每个层次结构,我都需要一个 for 循环。您将如何使用 jq 的语言递归遍历这棵树?

(我想使用 jq,因为我已经在使用它来将许多文件合并到第一个代码清单中的格式,所以我想我可以在此基础上进行构建。合并很简单:jq -s 'reduce .[] as $x (, . * $x)' *.json > merged.json

【问题讨论】:

【参考方案1】:

这是使用 to_entries 获取数据的解决方案,将其放入 setpath 将接受的形式,group_by 按日期组织和reduce 使用 setpath 构建最终形式。

您可以逐步了解其工作原理。先从

开始
  to_entries
| .[]
| .key as $k1
| ( .value | to_entries
           | $k1, .[] )

到达第一个键。用我的测试数据给我

"key00"

  "key": "key10",
  "value": 
    "20170405": 
      "val0": 10,
      "valn": 12
    ,
    "20170404": 
      "val0": 5,
      "valn": 43
    
  

"key01"
...

然后再向下钻取到下一个键

  to_entries
| .[]
| .key as $k1
| ( .value | to_entries
           | .[]
           | .key as $k2
           | ( .value | to_entries
                      | $k1, $k2, .[] ) ) 

给了

"key00"
"key10"

  "key": "20170405",
  "value": 
    "val0": 10,
    "valn": 12
  


  "key": "20170404",
  "value": 
    "val0": 5,
    "valn": 43
  

"key01"
"key11"
...

然后再多一点来获取日期和最终值

  to_entries
| .[]
| .key as $k1
| ( .value | to_entries
           | .[]
           | .key as $k2
           | ( .value | to_entries
                      | .[]
                      | .key as $d
                      | ( .value | to_entries
                                 | .[]
                                 | [$d, [$k1, $k2, .key], .value] ) ) )

现在我们有了

[
  "20170405",
  [
    "key00",
    "key10",
    "val0"
  ],
  10
]
[
  "20170405",
  [
    "key00",
    "key10",
    "valn"
  ],
  12
]
...

将其放回数组中并使用 group_byreducesetpath

[
  to_entries
| .[]
| .key as $k1
| ( .value | to_entries
           | .[]
           | .key as $k2
           | ( .value | to_entries
                      | .[]
                      | .key as $d
                      | ( .value | to_entries
                                 | .[]
                                 | [$d, [$k1, $k2, .key], .value]
                        )                
             )
   )
]
| group_by(.[0])
| .[]
| .[0][0] as $d
| reduce .[] as $e (
      date:$d
    ; setpath([$e[1] | join(".")]; $e[2])
  )

得到最终答案


  "date": "20170404",
  "key00.key10.val0": 5,
  "key00.key10.valn": 43


  "date": "20170405",
  "key01.key11.val1": 1,
  "key00.key10.valn": 12,
  "key00.key10.val0": 10,
  "key01.key11.val2": 2


  "date": "20170406",
  "key01.key11.val0": 0,
  "key01.key11.val9": 9

...

【讨论】:

【参考方案2】:

这似乎是一个艰难的过程,但为了让事情更容易理解,让我们从一个辅助函数开始使用连接字符:

# input: [arrayOfStrings, value]
def squish(joinchar):  (.[0] | join(joinchar)): .[1] ;

例如,[["a","b"], 10] | squish(".") 发出 "a.b", 10

该问题的其余解决方案是基于内置过滤器pathsgroup_by,它们在其他地方有文档,但简而言之,paths 发出代表路径的字符串数组流;然后附加相关的值。然后使用 group_by 按日期对 [path, value] 数组进行分组。最后根据要求对结果进行格式化。

. as $in
| [paths 
   | select(length==4)
   | . as $path
   | [ $path, ($in|getpath($path)) ] ]
| group_by( .[0][2] | tonumber )   # sort by numeric value
| map( date: .[0][0][2] 
        + ( map( del(.[0][2]) | squish(".") ) | add) )

注意事项

    上述解决方案按日期对路径进行全局分组,除了示例输出数据外,这似乎符合要求。

    如果数据与给定样本不同,则可能必须修改上面使用的select(length==4) 标准。

【讨论】:

这很有趣。我正在消化理解它。但是,w.r.t.您的警告 1,是的,这是示例数据中的错字。我已经更正了。 这为我处理输入数据的方式打开了一扇全新的大门。谢谢! 我有它的工作,我基本上理解了整个序列。我没有得到的是索引。例如group_by(.[0][2]),文档不清楚,我认为这意味着查看数组中的第一个元素,它是第三个元素,而不是按相同元素对数组第一级中的其余元素进行分组。 .[*][2] 之类的东西是否受支持? .[0][2].[0] | .[2] 的简称。如果您不理解文档和示例,那么我建议您使用 jq 和/或 jqplay(例如,为了更好地理解内置过滤器或管道)。战略性地放置debugs 通常也很有帮助。

以上是关于使用 jq 将嵌套的 JSON 文件分解为具有唯一键的平面列表的主要内容,如果未能解决你的问题,请参考以下文章

JQ将数据从1个文件推送到另一个文件的嵌套数组中

使用 JQ 展平嵌套的 Json 对象

如何使用 JQ 转换此 JSON 数据以依次将每个嵌套数组元素提取到顶层?

使用poseexplode分解带有索引的嵌套JSON

使用 jq 为 JSON 对象的嵌套数组中的属性展平数组

使用 jq 展平嵌套的 JSON