如何通过jq有条件地为匹配/非匹配JSON子树应用不同函数?
jq实现JSON树节点差异化处理:特定路径后代用专属函数,其余节点用另一个函数
需求说明
要给JSON树里的节点分情况处理:如果节点是.deep.path.matching的后代,就执行change_x(把x字段改成"changed");其他所有节点执行delete_y(删除y字段)。用户用YAML读写,但需要jq的实现方案,要求尽量简洁或高效(避免二次遍历)。
示例输入输出
输入YAML
top: y: "to-delete" x: "keep-original" deep: path: matching: child1: x: "need-change" y: "should-remain" child2: y: "should-remain" nested: x: "need-change-too" non-matching: x: "keep-original" y: "to-delete"
期望输出YAML
top: x: "keep-original" deep: path: matching: child1: x: "changed" y: "should-remain" child2: y: "should-remain" nested: x: "changed" non-matching: x: "keep-original"
实现方案
1. 简洁版(优先可读性)
用jq内置的walk遍历所有节点,结合path函数判断当前节点是否属于目标路径的后代。写法简单,适合大多数场景。
# 定义两个处理函数 def change_x: .x = "changed"; def delete_y: del(.y); # 核心逻辑:遍历每个节点,判断路径后应用对应函数 walk( if (path | startswith(["deep", "path", "matching"])) then change_x else delete_y end )
说明:walk会递归遍历JSON树的每个节点,path获取当前节点的路径数组(比如.deep.path.matching.child1的路径是["deep","path","matching","child1"]),startswith判断是否以目标路径前缀开头,然后分别执行函数。唯一缺点是大文档下,每次调用path会重新计算路径,效率略低,但日常使用完全足够。
2. 高效版(优先性能,避免重复计算路径)
手动写递归逻辑,遍历过程中传递当前路径,避免重复计算路径,适合处理大型JSON树。
# 定义处理函数 def change_x: .x = "changed"; def delete_y: del(.y); # 递归处理函数,参数:当前节点、当前路径数组 def process($path): if type == "object" then # 判断是否是目标路径的后代 if $path | startswith(["deep", "path", "matching"]) then # 遍历对象的每个键,追加到路径后递归处理,最后应用change_x .[] |= process($path + [keys[]]) | change_x else .[] |= process($path + [keys[]]) | delete_y end elif type == "array" then # 数组元素的路径追加索引,递归处理后应用对应函数 .[] |= process($path + [index(.)]) | ( $path | startswith(["deep", "path", "matching"]) ? change_x : delete_y ) else # 非容器节点(字符串、数字等),直接判断路径应用函数 $path | startswith(["deep", "path", "matching"]) ? change_x : delete_y end; # 初始调用,路径为空数组 process([])
说明:手动递归时,每次处理子节点都会把当前路径传递下去,不用重复计算路径,性能比walk版更高。需要分别处理对象和数组的路径追加逻辑,写法稍复杂,但大文档下优势明显。
使用命令链
因为用户用YAML读写,需要先把YAML转成JSON给jq处理,再转回YAML,比如用yq工具:
yq -o json input.yaml | jq -f script.jq | yq -o yaml -
内容的提问来源于stack exchange,提问作者yaccob
相关产品推荐
相关产品推荐

