如何使用 jq 合并来自两个文件的两个 JSON 对象?

238

我在shell脚本中使用jq工具(jq-json-processor)解析json。

我有两个json文件,希望将它们合并为一个唯一的文件

以下是这些文件的内容:

file1

{
    "value1": 200,
    "timestamp": 1382461861,
    "value": {
        "aaa": {
            "value1": "v1",
            "value2": "v2"
        },
        "bbb": {
            "value1": "v1",
            "value2": "v2"
        },
        "ccc": {
            "value1": "v1",
            "value2": "v2"
        }
    }
}

文件2

{
    "status": 200,
    "timestamp": 1382461861,
    "value": {
        "aaa": {
            "value3": "v3",
            "value4": 4
        },
        "bbb": {
            "value3": "v3"
        },      
        "ddd": {
            "value3": "v3",
            "value4": 4
        }
    }
}

预期结果

{
    "value": {
        "aaa": {
            "value1": "v1",
            "value2": "v2",
            "value3": "v3",
            "value4": 4
        },
        "bbb": {
            "value1": "v1",
            "value2": "v2",
            "value3": "v3"
        },
        "ccc": {
            "value1": "v1",
            "value2": "v2"
        },
        "ddd": {
            "value3": "v3",
            "value4": 4
        }
    }
}

我尝试了很多组合,但是我得到的唯一结果是下面这个,它不是期望的结果:

{
  "ccc": {
    "value2": "v2",
    "value1": "v1"
  },
  "bbb": {
    "value2": "v2",
    "value1": "v1"
  },
  "aaa": {
    "value2": "v2",
    "value1": "v1"
  }
}
{
  "ddd": {
    "value4": 4,
    "value3": "v3"
  },
  "bbb": {
    "value3": "v3"
  },
  "aaa": {
    "value4": 4,
    "value3": "v3"
  }
}

使用这个命令:

jq -s '.[].value' file1 file2

2
你尝试过jsontool吗?https://github.com/trentm/json - Nano Taboada
2
要使用 json 完成此操作,请使用命令:cat f1 f2 | json --deep-merge - xer0x
@xer0x,你从哪儿/如何获取 json - Gus
1
@Gus 哦,要获取 json 工具,请访问 https://github.com/trentm/json - xer0x
8个回答

292

从1.4版本开始,可以使用*运算符实现此功能。当给定两个对象时,它将递归地合并它们。例如:

jq -s '.[0] * .[1]' file1 file2

重要提示: 注意使用-s (--slurp)标志,可以将文件放在同一个数组中。

使用该命令会得到以下结果:

{
  "value1": 200,
  "timestamp": 1382461861,
  "value": {
    "aaa": {
      "value1": "v1",
      "value2": "v2",
      "value3": "v3",
      "value4": 4
    },
    "bbb": {
      "value1": "v1",
      "value2": "v2",
      "value3": "v3"
    },
    "ccc": {
      "value1": "v1",
      "value2": "v2"
    },
    "ddd": {
      "value3": "v3",
      "value4": 4
    }
  },
  "status": 200
}

如果您还想摆脱其他键(如您期望的结果),一种方法是这样做:
jq -s '.[0] * .[1] | {value: .value}' file1 file2

或者更有效的方式(因为它不会合并任何其他值):
jq -s '.[0].value * .[1].value | {value: .}' file1 file2

2
注意:-s标志很重要,如果没有它,这两个对象不会组成一个数组。 - John Morales
1
@SimoKinnunen 这里我们正在合并两个json文件。是否可能有一个json变量和另一个json文件。 我尝试过了,但似乎对我不起作用! - Jayesh Dhandha
@SimoKinnunen 我正在使用以下命令: "jq -s 'add' config.json other/*.json" 但是当我执行 "cat config.json" 时,它并没有合并。我该如何将输出放回文件中? - Renato Bibiano
1
请注意,{value: .value} 可以缩写为 {value} - Mark Reed
1
对于@JayeshDhandha和未来的读者,合并一个变量和一个文件可以像这样完成:jq ". * $json_variable" json_input_file.json > json_output_file.json - Walf
显示剩余2条评论

120

使用jq -s add命令:

$ echo '{"a":"foo","b":"bar"} {"c":"baz","a":0}' | jq -s add
{
  "a": 0,
  "b": "bar",
  "c": "baz"
}

这将从stdin读取所有JSON文本并将其存储在一个数组中(jq -s可以实现),然后"reduce"它们。

(add被定义为def add: reduce .[] as $x (null; . + $x);,它遍历输入数组/对象的值并将它们相加。对象相加==合并.)


5
使用这种方法能否进行递归合并(*运算符)? - Dave Foster
4
是的,可以使用类似于reduce .[] as $x ({}; . * $x)这样的语法 - 参见jrib的答案 - Chriki
echo '{"a":1}' | jq -s '[.[0], {"b":$b}] | add' --argjson b 2 - milahu

59

这是一个递归版本(使用 *)作用于任意数量的对象:

echo '{"A": {"a": 1}}' '{"A": {"b": 2}}' '{"B": 3}' |\
  jq --slurp 'reduce .[] as $item ({}; . * $item)'

{
  "A": {
    "a": 1,
    "b": 2
  },
  "B": 3
}

5
很棒的答案。在合并目录中所有文件时有效:jq -s 'reduce .[] as $item ({}; . * $item)' *.json - John Ellmore
1
我的 JSON 内容是对象数组,所以这个命令适用于我: echo $JSON_ONE $JSON_TWO | jq -s 'flatten | group_by(keys[]) | map(reduce .[] as $item ({}; . * $item))' - Nik

47

谁知道你是否还需要,但这里是解决方案。

一旦你使用--slurp选项,就很容易了!

--slurp/-s:
    Instead of running the filter for each JSON object in the input,
    read the entire input stream into a large array and run the filter just once.

然后使用+运算符即可实现你想要的功能:

jq -s '.[0] + .[1]' config.json config-user.json

(注意:如果您想合并内部对象,而不仅仅是用右文件覆盖左文件的内容,则需要手动执行)


谢谢...关于最右侧输入优先级的提示真的很有用。 - Mark Crossfield

24

截止目前为止,没有任何解决方案或评论考虑使用input来访问第二个文件。如果使用--slurp(或-s)选项时构建一个额外的结构进行提取将变得不必要,该选项几乎出现在所有其他方法中,例如包容性数组。

要在顶层合并两个文件,只需使用+input中的第二个文件添加到第一个文件中的.中:

jq '. + input' file1.json file2.json

要在所有级别上递归地合并两个文件,请使用 * 作为运算符执行相同的操作:

jq '. * input' file1.json file2.json

话虽如此,要递归合并你的两个文件,并将两个对象都简化为它们的value字段,首先使用{value}对它们进行过滤:

jq '{value} * (input | {value})' file1.json file2.json
{
  "value": {
    "aaa": {
      "value1": "v1",
      "value2": "v2",
      "value3": "v3",
      "value4": 4
    },
    "bbb": {
      "value1": "v1",
      "value2": "v2",
      "value3": "v3"
    },
    "ccc": {
      "value1": "v1",
      "value2": "v2"
    },
    "ddd": {
      "value3": "v3",
      "value4": 4
    }
  }
}

演示

请注意,仅在合并后减少的解决方案(例如. * input | {value})在代码上更短,但会再次产生“构建额外结构以提取”的无用工作,如果最终要削减的部分很大,则可能会产生很多开销。

为了处理两个以上的文件,请使用input多次,或者使用inputs进行编程迭代,如下所示:

jq 'reduce inputs as $i (.; . * $i)' file*.json

注意,无论哪种情况,第一个文件始终通过输入上下文 . 访问,而 input(s) 仅处理其他文件,即从第二个文件开始(当然,除非给出了 --null-input-n 选项)。


9

首先,{"value": .value} 可以简写为 {value}。

其次,--argfile 选项(在 jq 1.4 和 jq 1.5 中可用)可能会很有用,因为它避免了使用 --slurp 选项。

将这些组合在一起,可以按以下方式将两个文件中的两个对象组合:

$ jq -n --argfile o1 file1 --argfile o2 file2 '$o1 * $o2 | {value}'

'-n' 标志告诉 jq 不要从 stdin 中读取,因为输入是来自这里的 --argfile 选项。

关于 --argfile 的注意事项:

jq 手册弃用了 --argfile,因为其语义非常复杂:如果指定的输入文件恰好包含一个 JSON 实体,则该实体会原样读取;否则,流中的项目将被包装在数组中。

如果您不想使用 --argfile,可以考虑几种替代方案。请放心使用 --slurpfile,当与多个文件一起使用时,它并不会产生 -s 命令行选项的低效率问题。

3
jq已经弃用--argile,改为使用--slurpfile - David Groomes

8

这可以用于合并命令中指定的任意数量的文件:

jq -rs 'reduce .[] as $item ({}; . * $item)' file1.json file2.json file3.json ... file10.json

或者用于任意数量的文件:

jq -rs 'reduce .[] as $item ({}; . * $item)' ./*.json


5

我不想在我的对象中丢弃以前的非唯一键

jq -n '{a:1, c:2}, {b:3, d:4}, {a:5,d:6}' |
jq -s 'map(to_entries)|flatten|group_by(.key)|map({(.[0].key):map(.value)|add})|add'
{
  "a": 6,
  "b": 3,
  "c": 2,
  "d": 10
}

或者,如果您只想保留值的数组,请在提取值后删除添加操作map(.value)|̶a̶d̶d̶

jq -n '{a:1, c:2}, {b:3, d:4}, {a:5,d:6}' |
jq -s 'map(to_entries)|flatten|group_by(.key)|map({(.[0].key):map(.value)})|add'
{
  "a": [1, 5],
  "b": [3],
  "c": [2],
  "d": [4, 6]
}

尝试逐步删除命令的每个部分,看看每个步骤如何修改对象数组...也就是运行这些步骤并观察输出如何变化。

map(to_entries)
map(to_entries)|flatten
map(to_entries)|flatten|group_by(.key)
map(to_entries)|flatten|group_by(.key)|map({(.[0].key):map(.value)})
map(to_entries)|flatten|group_by(.key)|map({(.[0].key):map(.value)})|add

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