如何用jq保留父键层级切片嵌套JSON对象?
解决jq路径过滤器保留完整父层级的问题
嘿,我明白你想要的效果——不用两次调用jq,直接通过一次jq处理,从指定的路径过滤器反向构建出包含所有父层级的JSON结构对吧?其实用jq本身的getpath和reduce就能轻松实现,而且完全适配任意合法的JSON对象和路径过滤器。
核心思路
我们需要把路径过滤器(比如.a.b.c)拆解成键数组,获取目标路径对应的值,然后从叶子节点往根节点逐层嵌套,最终得到包含完整父层级的JSON结构。
实现方案
单jq命令实现
直接用以下命令就能完成需求,把Bash变量F(路径过滤器)和O(JSON对象)传入即可:
jq --arg filter "$F" ' # 把路径过滤器转换为键数组(去掉开头的.,再按.分割) ($filter | sub("^\\."; "") | split(".")) as $keys # 获取目标路径对应的值 | getpath($keys) as $value # 反向遍历键数组,逐层构建嵌套对象 | reduce ($keys | reverse[]) as $key ( $value; { ($key): . } ) ' <<< "$O"
测试示例
用你给出的例子测试:
O=' { "a": { "b": { "c": { "p":1 }, "x": 1 }, "x": 2 }, "x": 3 }' F='.a.b.c'
执行上面的命令后,输出结果正好是你期望的:
{ "a": { "b": { "c": { "p": 1 } } } }
代码解释
--arg filter "$F":将Bash中的路径过滤器变量F传递给jq,命名为$filter。$filter | sub("^\\."; "") | split("."):处理路径字符串,去掉开头的.,再按.分割成键数组(比如.a.b.c会变成["a","b","c"])。getpath($keys):通过键数组获取JSON对象中对应路径的值(这里就是{"p":1})。reduce ($keys | reverse[]) as $key ($value; { ($key): . }):反向遍历键数组(从c到b再到a),每次把当前结果包裹到以当前键为名称的对象中,最终构建出完整的父层级结构。
边界情况适配
- 如果路径过滤器是根节点(
F="."):此时键数组为空,reduce不会执行,直接返回整个JSON对象,符合预期。 - 如果路径不存在(比如
F=".a.b.d"):getpath会返回null,最终结果会生成包含该路径的结构,对应值为null,逻辑合理。
封装成Bash函数(可选)
如果需要频繁使用,可以封装成一个Bash函数:
jq_nested_filter() { local filter="$1" local json="$2" jq --arg filter "$filter" ' ($filter | sub("^\\."; "") | split(".")) as $keys | getpath($keys) as $value | reduce ($keys | reverse[]) as $key ( $value; { ($key): . } ) ' <<< "$json" }
调用方式:
jq_nested_filter ".a.b.c" "$O"
内容的提问来源于stack exchange,提问作者Steve Amerige
相关产品推荐
相关产品推荐

