如何合并2 JSON对象从2个文件使用jq?

我使用shell脚本中的金桥工具(jq-json-processor)来解析json。

我有2个json文件和想把它们合并成一个唯一的文件

这里是文件的内容:

file1

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

file2

{
"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
165118 次浏览

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

一旦你得到--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

(注意:如果你想合并内部对象,而不是用右边的文件覆盖左边的文件,你需要手动操作)

使用jq -s add:

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

它将所有JSON文本从stdin读入一个数组(jq -s这样做),然后“减少”它们。

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

从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

首先,{"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命令行选项在与多个文件一起使用时效率低下。

下面是一个版本,它可以递归地(使用*)处理任意数量的对象:

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


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

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

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

或者对于任意数量的文件

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

到目前为止,没有任何解决方案或评论考虑使用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
}
}
}

Demo

请注意,像. * input | {value}这样只减少合并的解决方案,代码更短,但会重新“构建一个额外的结构,从中提取”。又是徒劳无功,这可能会产生大量的开销,如果最终被切断的部分变得很大。

为了操作两个以上的文件,要么相应地多次使用input,要么以编程方式使用inputs遍历所有文件,如

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

注意,在这两种情况下,第一个文件总是通过输入上下文.访问,而input(s)只处理剩下的文件,即从第二个文件开始(当然,除非给出了--null-input-n选项)。

我不想放弃之前的非唯一键在我的对象

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̶之后删除add

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