jq: 递归合并对象和连接数组

10
我有两个json文件,orig.json和patch.json,它们的格式相似。

orig.json:

{
        "a": {
                "a1": "original a1",
                "a2": "original a2",
                "list": ["baz", "bar"]
        },
        "b": "original value B"
}

patch.json:

{
    "a": {
            "a1": "patch a1",
            "list": ["foo"]
    },
    "c": "original c"
}

目前我正在使用jq进行递归合并。但是,jq对于列表的默认行为只是重新分配。使用$ jq -s'.[0] * .[1]' orig.json patch.json命令在jq中的示例输出:

{
  "a": {
    "a1": "patch a1",
    "a2": "original a2",
    "list": [
      "foo"
    ]
  },
  "b": "original value B",
  "c": "original c"
}

请注意,a.list现在等于patch.json的a.list。我希望新的a.list是orig.json的列表和patch.json的列表合并而成的。换句话说,我希望a.list等于["baz", "bar", "foo"]
是否有一种简单的方法可以使用jq来实现这一点,也许通过覆盖数组的默认合并策略?
3个回答

7
$ jq -s 'def deepmerge(a;b):
  reduce b[] as $item (a;
    reduce ($item | keys_unsorted[]) as $key (.;
      $item[$key] as $val | ($val | type) as $type | .[$key] = if ($type == "object") then
        deepmerge({}; [if .[$key] == null then {} else .[$key] end, $val])
      elif ($type == "array") then
        (.[$key] + $val | unique)
      else
        $val
      end)
    );
  deepmerge({}; .)' file1.json file2.json > merged.json

7
以下是一种通用函数,它通过将在同一位置的数组连接起来来递归地组合两个复合JSON实体:
# Recursively meld a and b,
# concatenating arrays and
# favoring b when there is a conflict 
def meld(a; b):
  a as $a | b as $b
  | if ($a|type) == "object" and ($b|type) == "object"
    then reduce ([$a,$b]|add|keys_unsorted[]) as $k ({}; 
      .[$k] = meld( $a[$k]; $b[$k]) )
    elif ($a|type) == "array" and ($b|type) == "array"
    then $a+$b
    elif $b == null then $a
    else $b
    end;

meld($orig; $patch)的输出结果

当$orig设置为orig.json的内容,而$patch设置为patch.json的内容时:

{
  "a": {
    "a1": "patch a1",
    "a2": "original a2",
    "list": [
      "baz",
      "bar",
      "foo"
    ]
  },
  "b": "original value B",
  "c": "original c"
}

我不确定如何直接评论编辑:参数中的 $ 更为简洁,我认为更易理解,这也是我提出建议的原因;如果您想使用 a as $a 构造,我认为在 reduce 之前执行此操作会更整洁,以便更清楚地表明该值由于某种原因被锁定。 - Parakleta
在所有现有版本的jq中,不支持在定义的参数中使用$变量。将$变量定义放在最前面主要是为了提高效率和清晰度。 - peak
好的,我没有意识到$参数并非在所有地方都可用。谢谢你的澄清。 - Parakleta
1
我们如何使用这个函数?你能给一个示例命令吗? - Kos Prov
2
@KosProv:似乎运行的方式是jq -f meld.jq 1.json 2.json ..,其中文件meld.jq中包含函数定义,后跟所需的调用语句(我使用reduce inputs as $i (.; meld(.; $i))将多个JSON文件进行深度合并而不需要 slurping)...小心分号分隔参数;逗号会导致误导性错误meld/1 is not defined at <top-level>, line 16: - eMPee584

0

调用:

jq -f patch.jq --argfile patch patch.json  orig.json

其中 patch.jq 包含:

(.a.list + $patch.a.list) as $a
| . + $patch
| .a.list = $a

产生:

{
  "a": {
    "a1": "patch a1",
    "list": [
      "baz",
      "bar",
      "foo"
    ]
  },
  "b": "original value B",
  "c": "original c"
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接