使用JQ递归移除空数组与空对象对应键的问题排查
问题描述
输入JSON:
{ "a": { "a": "foo", "b": "bar" }, "b": { "a": [], "b": [] }, "c": {}, "d": [ "foo", "bar" ] }
期望结果:递归移除所有指向空数组或空对象的键,得到:
{ "a": { "a": "foo", "b": "bar" }, "d": [ "foo", "bar" ] }
尝试的JQ命令:
jq 'walk(if ((type=="object" or type=="array") and length==0) then empty else . end)'
错误结果:仅保留最后一个数组:
{ "d": [ "foo", "bar" ] }
将命令中的empty替换为{"A":1}后:
jq 'walk(if ((type=="object" or type=="array") and length==0) then {"A":1} else . end)'
得到符合预期的中间结果(非空对象未被修改):
{ "a": { "a": "foo", "b": "bar" }, "b": { "a": { "A": 1 }, "b": { "A": 1 } }, "c": { "A": 1 }, "d": [ "foo", "bar" ] }
问题原因分析
核心问题在于empty的特殊性和walk函数的执行逻辑:
walk的执行顺序是先递归处理所有子元素,再对当前元素应用过滤函数。- 当你用
empty替换空数组/空对象时,对象中值为empty的键会被直接丢弃。比如处理b对象的子键a和b(都是空数组)时,它们会被替换为empty,导致b对象失去所有子键,变成空对象。 - 这个新生成的空对象会再次被过滤函数检测到,符合
type=="object" and length==0的条件,进而被替换为empty,最终导致顶层的b键被移除。 - 若你的非空对象(如
a)也被误删,大概率是命令逻辑存在疏漏(比如条件判断错误)或使用了旧版本jq的walk实现,但从替换empty为{"A":1}的测试结果来看,a对象本身是符合保留条件的,误删可能是测试过程中的操作失误。
正确解决方案
要实现递归移除空数组/空对象的键,需要调整逻辑:只清理对象中值为空的键,而非直接替换空结构为empty。推荐使用以下命令:
jq 'walk( if type == "object" then with_entries(select(.value | (type != "array" and type != "object") or length > 0)) elif type == "array" then map(select((type != "array" and type != "object") or length > 0)) else . end )'
逻辑说明
- 处理对象时:通过
with_entries(select(...))筛选出值不是空数组/空对象的键; - 处理数组时:通过
map(select(...))保留非空结构的元素; - 非对象/数组的元素直接保留,不做修改。
执行该命令后,会得到你期望的结果:
{ "a": { "a": "foo", "b": "bar" }, "d": [ "foo", "bar" ] }
内容的提问来源于stack exchange,提问作者wfaulk
相关产品推荐
相关产品推荐

