使用 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_by、reduce 和 setpath
[
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
该问题的其余解决方案是基于内置过滤器paths
和group_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(例如,为了更好地理解内置过滤器或管道)。战略性地放置debug
s 通常也很有帮助。以上是关于使用 jq 将嵌套的 JSON 文件分解为具有唯一键的平面列表的主要内容,如果未能解决你的问题,请参考以下文章