使用 jq 或其他命令行工具比较 JSON 个文件
Using jq or alternative command line tools to compare JSON files
是否有任何命令行实用程序可用于查找两个 JSON 文件是否相同且具有字典内键和列表元素内排序的不变性?
这可以用 jq
或其他等效工具来完成吗?
示例:
这两个 JSON 文件是相同的
A
:
{
"People": ["John", "Bryan"],
"City": "Boston",
"State": "MA"
}
B
:
{
"People": ["Bryan", "John"],
"State": "MA",
"City": "Boston"
}
但这两个 JSON 文件是不同的:
A
:
{
"People": ["John", "Bryan", "Carla"],
"City": "Boston",
"State": "MA"
}
C
:
{
"People": ["Bryan", "John"],
"State": "MA",
"City": "Boston"
}
那就是:
$ some_diff_command A.json B.json
$ some_diff_command A.json C.json
The files are not structurally identical
由于 jq 的比较已经在不考虑键顺序的情况下比较了对象,所以剩下的就是在比较对象之前对对象内的所有列表进行排序。假设你的两个文件分别命名为 a.json
和 b.json
,在最新的 jq nightly 上:
jq --argfile a a.json --argfile b b.json -n '($a | (.. | arrays) |= sort) as $a | ($b | (.. | arrays) |= sort) as $b | $a == $b'
此程序应该 return "true" 或 "false" 取决于使用您要求的相等性定义对象是否相等。
编辑:(.. | arrays) |= sort
构造在某些边缘情况下实际上并没有按预期工作。 This GitHub issue 解释了原因并提供了一些替代方案,例如:
def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); (post_recurse | arrays) |= sort
应用于上面的 jq 调用:
jq --argfile a a.json --argfile b b.json -n 'def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); ($a | (post_recurse | arrays) |= sort) as $a | ($b | (post_recurse | arrays) |= sort) as $b | $a == $b'
这是一个使用通用函数 walk/1 的解决方案:
# Apply f to composite entities recursively, and to atoms
def walk(f):
. as $in
| if type == "object" then
reduce keys[] as $key
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
elif type == "array" then map( walk(f) ) | f
else f
end;
def normalize: walk(if type == "array" then sort else . end);
# Test whether the input and argument are equivalent
# in the sense that ordering within lists is immaterial:
def equiv(x): normalize == (x | normalize);
示例:
{"a":[1,2,[3,4]]} | equiv( {"a": [[4,3], 2,1]} )
产生:
true
并封装为 bash 脚本:
#!/bin/bash
JQ=/usr/local/bin/jq
BN=$(basename [=13=])
function help {
cat <<EOF
Syntax: [=13=] file1 file2
The two files are assumed each to contain one JSON entity. This
script reports whether the two entities are equivalent in the sense
that their normalized values are equal, where normalization of all
component arrays is achieved by recursively sorting them, innermost first.
This script assumes that the jq of interest is $JQ if it exists and
otherwise that it is on the PATH.
EOF
exit
}
if [ ! -x "$JQ" ] ; then JQ=jq ; fi
function die { echo "$BN: $@" >&2 ; exit 1 ; }
if [ $# != 2 -o "" = -h -o "" = --help ] ; then help ; exit ; fi
test -f "" || die "unable to find "
test -f "" || die "unable to find "
$JQ -r -n --argfile A "" --argfile B "" -f <(cat<<"EOF"
# Apply f to composite entities recursively, and to atoms
def walk(f):
. as $in
| if type == "object" then
reduce keys[] as $key
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
elif type == "array" then map( walk(f) ) | f
else f
end;
def normalize: walk(if type == "array" then sort else . end);
# Test whether the input and argument are equivalent
# in the sense that ordering within lists is immaterial:
def equiv(x): normalize == (x | normalize);
if $A | equiv($B) then empty else "\($A) is not equivalent to \($B)" end
EOF
)
POSTSCRIPT:walk/1 是 jq > 1.5 的内置版本,因此如果您的 jq 包含它,则可以省略它,但是将它冗余地包含在一个 jq 脚本。
POST-POSTSCRIPT:walk
的内置版本最近已更改,因此它不再对对象中的键进行排序。具体来说,它使用 keys_unsorted
。对于手头的任务,应该使用使用 keys
的版本。
如果您的 shell 支持进程替换(Bash 样式如下,请参阅 docs):
diff <(jq --sort-keys . A.json) <(jq --sort-keys . B.json)
对象键顺序将被忽略,但数组顺序仍然重要。如果需要,可以通过以其他方式对数组值进行排序,或使它们类似集合(例如 ["foo", "bar"]
→ {"foo": null, "bar": null}
;这也将删除重复项)来解决这个问题。
或者,将 diff
替换为其他一些比较器,例如cmp
、colordiff
或 vimdiff
,具体取决于您的需要。如果您只想回答是或否,请考虑使用 cmp
并将 --compact-output
传递给 jq
以不格式化输出以获得潜在的小性能提升。
如果您还想查看差异,请使用@Erik 的回答作为灵感,js-beautify:
$ echo '[{"name": "John", "age": 56}, {"name": "Mary", "age": 67}]' > file1.json
$ echo '[{"age": 56, "name": "John"}, {"name": "Mary", "age": 61}]' > file2.json
$ diff -u --color \
<(jq -cS . file1.json | js-beautify -f -) \
<(jq -cS . file2.json | js-beautify -f -)
--- /dev/fd/63 2016-10-18 13:03:59.397451598 +0200
+++ /dev/fd/62 2016-10-18 13:03:59.397451598 +0200
@@ -2,6 +2,6 @@
"age": 56,
"name": "John Smith"
}, {
- "age": 67,
+ "age": 61,
"name": "Mary Stuart"
}]
使用 jd
和 -set
选项:
无输出表示无差别
$ jd -set A.json B.json
差异显示为 @ 路径和 + 或 -。
$ jd -set A.json C.json
@ ["People",{}]
+ "Carla"
输出差异也可以通过 -p
选项用作补丁文件。
$ jd -set -o patch A.json C.json; jd -set -p patch B.json
{"City":"Boston","People":["John","Carla","Bryan"],"State":"MA"}
也许你可以使用这个排序和差异工具:http://novicelab.org/jsonsortdiff/ which first sorts the objects semantically and then compares it. It is based on https://www.npmjs.com/package/jsonabc
从前两个答案中提取最佳答案以获得基于 jq
的 json 差异:
diff \
<(jq -S 'def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); (. | (post_recurse | arrays) |= sort)' "$original_json") \
<(jq -S 'def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); (. | (post_recurse | arrays) |= sort)' "$changed_json")
这采用了 (which allows us to treat arrays as sets) and the clean bash redirection into diff
from 中优雅的数组排序解决方案 这解决了您想要比较两个 json 文件并且数组内容的顺序不相关的情况。
这个 here 有一个有用的答案。
基本上您可以使用 Git diff
功能(即使是非 Git 跟踪文件),它在输出中还包括颜色:
git diff --no-index payload_1.json payload_2.json
对于之前的答案不太适合的人,还有一个工具,您可以尝试jdd。
它基于 HTML,因此您可以在 www.jsondiff.com 上在线使用它,或者,如果您更喜欢 运行 在本地使用它,只需下载项目并打开 index.html .
在JSONiq中,你可以简单地使用deep-equal函数:
deep-equal(
{
"People": ["John", "Bryan", "Carla"],
"City": "Boston",
"State": "MA"
},
{
"People": ["Bryan", "John"],
"State": "MA",
"City": "Boston"
}
)
哪个returns
false
您也可以像这样从文件中读取(本地或 HTTP URL 也可以):
deep-equal(
json-doc("path to doc A.json"),
json-doc("path to doc B.json")
)
一个可能的实现是RumbleDB。
但是,您需要注意,前两个文档相同的说法并不完全正确:JSON 将数组定义为有序的值列表。
["Bryan", "John"]
不等于:
["John", "Bryan"]
是否有任何命令行实用程序可用于查找两个 JSON 文件是否相同且具有字典内键和列表元素内排序的不变性?
这可以用 jq
或其他等效工具来完成吗?
示例:
这两个 JSON 文件是相同的
A
:
{
"People": ["John", "Bryan"],
"City": "Boston",
"State": "MA"
}
B
:
{
"People": ["Bryan", "John"],
"State": "MA",
"City": "Boston"
}
但这两个 JSON 文件是不同的:
A
:
{
"People": ["John", "Bryan", "Carla"],
"City": "Boston",
"State": "MA"
}
C
:
{
"People": ["Bryan", "John"],
"State": "MA",
"City": "Boston"
}
那就是:
$ some_diff_command A.json B.json
$ some_diff_command A.json C.json
The files are not structurally identical
由于 jq 的比较已经在不考虑键顺序的情况下比较了对象,所以剩下的就是在比较对象之前对对象内的所有列表进行排序。假设你的两个文件分别命名为 a.json
和 b.json
,在最新的 jq nightly 上:
jq --argfile a a.json --argfile b b.json -n '($a | (.. | arrays) |= sort) as $a | ($b | (.. | arrays) |= sort) as $b | $a == $b'
此程序应该 return "true" 或 "false" 取决于使用您要求的相等性定义对象是否相等。
编辑:(.. | arrays) |= sort
构造在某些边缘情况下实际上并没有按预期工作。 This GitHub issue 解释了原因并提供了一些替代方案,例如:
def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); (post_recurse | arrays) |= sort
应用于上面的 jq 调用:
jq --argfile a a.json --argfile b b.json -n 'def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); ($a | (post_recurse | arrays) |= sort) as $a | ($b | (post_recurse | arrays) |= sort) as $b | $a == $b'
这是一个使用通用函数 walk/1 的解决方案:
# Apply f to composite entities recursively, and to atoms
def walk(f):
. as $in
| if type == "object" then
reduce keys[] as $key
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
elif type == "array" then map( walk(f) ) | f
else f
end;
def normalize: walk(if type == "array" then sort else . end);
# Test whether the input and argument are equivalent
# in the sense that ordering within lists is immaterial:
def equiv(x): normalize == (x | normalize);
示例:
{"a":[1,2,[3,4]]} | equiv( {"a": [[4,3], 2,1]} )
产生:
true
并封装为 bash 脚本:
#!/bin/bash
JQ=/usr/local/bin/jq
BN=$(basename [=13=])
function help {
cat <<EOF
Syntax: [=13=] file1 file2
The two files are assumed each to contain one JSON entity. This
script reports whether the two entities are equivalent in the sense
that their normalized values are equal, where normalization of all
component arrays is achieved by recursively sorting them, innermost first.
This script assumes that the jq of interest is $JQ if it exists and
otherwise that it is on the PATH.
EOF
exit
}
if [ ! -x "$JQ" ] ; then JQ=jq ; fi
function die { echo "$BN: $@" >&2 ; exit 1 ; }
if [ $# != 2 -o "" = -h -o "" = --help ] ; then help ; exit ; fi
test -f "" || die "unable to find "
test -f "" || die "unable to find "
$JQ -r -n --argfile A "" --argfile B "" -f <(cat<<"EOF"
# Apply f to composite entities recursively, and to atoms
def walk(f):
. as $in
| if type == "object" then
reduce keys[] as $key
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
elif type == "array" then map( walk(f) ) | f
else f
end;
def normalize: walk(if type == "array" then sort else . end);
# Test whether the input and argument are equivalent
# in the sense that ordering within lists is immaterial:
def equiv(x): normalize == (x | normalize);
if $A | equiv($B) then empty else "\($A) is not equivalent to \($B)" end
EOF
)
POSTSCRIPT:walk/1 是 jq > 1.5 的内置版本,因此如果您的 jq 包含它,则可以省略它,但是将它冗余地包含在一个 jq 脚本。
POST-POSTSCRIPT:walk
的内置版本最近已更改,因此它不再对对象中的键进行排序。具体来说,它使用 keys_unsorted
。对于手头的任务,应该使用使用 keys
的版本。
如果您的 shell 支持进程替换(Bash 样式如下,请参阅 docs):
diff <(jq --sort-keys . A.json) <(jq --sort-keys . B.json)
对象键顺序将被忽略,但数组顺序仍然重要。如果需要,可以通过以其他方式对数组值进行排序,或使它们类似集合(例如 ["foo", "bar"]
→ {"foo": null, "bar": null}
;这也将删除重复项)来解决这个问题。
或者,将 diff
替换为其他一些比较器,例如cmp
、colordiff
或 vimdiff
,具体取决于您的需要。如果您只想回答是或否,请考虑使用 cmp
并将 --compact-output
传递给 jq
以不格式化输出以获得潜在的小性能提升。
如果您还想查看差异,请使用@Erik 的回答作为灵感,js-beautify:
$ echo '[{"name": "John", "age": 56}, {"name": "Mary", "age": 67}]' > file1.json
$ echo '[{"age": 56, "name": "John"}, {"name": "Mary", "age": 61}]' > file2.json
$ diff -u --color \
<(jq -cS . file1.json | js-beautify -f -) \
<(jq -cS . file2.json | js-beautify -f -)
--- /dev/fd/63 2016-10-18 13:03:59.397451598 +0200
+++ /dev/fd/62 2016-10-18 13:03:59.397451598 +0200
@@ -2,6 +2,6 @@
"age": 56,
"name": "John Smith"
}, {
- "age": 67,
+ "age": 61,
"name": "Mary Stuart"
}]
使用 jd
和 -set
选项:
无输出表示无差别
$ jd -set A.json B.json
差异显示为 @ 路径和 + 或 -。
$ jd -set A.json C.json
@ ["People",{}]
+ "Carla"
输出差异也可以通过 -p
选项用作补丁文件。
$ jd -set -o patch A.json C.json; jd -set -p patch B.json
{"City":"Boston","People":["John","Carla","Bryan"],"State":"MA"}
也许你可以使用这个排序和差异工具:http://novicelab.org/jsonsortdiff/ which first sorts the objects semantically and then compares it. It is based on https://www.npmjs.com/package/jsonabc
从前两个答案中提取最佳答案以获得基于 jq
的 json 差异:
diff \
<(jq -S 'def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); (. | (post_recurse | arrays) |= sort)' "$original_json") \
<(jq -S 'def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); (. | (post_recurse | arrays) |= sort)' "$changed_json")
这采用了 diff
from
这个 here 有一个有用的答案。
基本上您可以使用 Git diff
功能(即使是非 Git 跟踪文件),它在输出中还包括颜色:
git diff --no-index payload_1.json payload_2.json
对于之前的答案不太适合的人,还有一个工具,您可以尝试jdd。
它基于 HTML,因此您可以在 www.jsondiff.com 上在线使用它,或者,如果您更喜欢 运行 在本地使用它,只需下载项目并打开 index.html .
在JSONiq中,你可以简单地使用deep-equal函数:
deep-equal(
{
"People": ["John", "Bryan", "Carla"],
"City": "Boston",
"State": "MA"
},
{
"People": ["Bryan", "John"],
"State": "MA",
"City": "Boston"
}
)
哪个returns
false
您也可以像这样从文件中读取(本地或 HTTP URL 也可以):
deep-equal(
json-doc("path to doc A.json"),
json-doc("path to doc B.json")
)
一个可能的实现是RumbleDB。
但是,您需要注意,前两个文档相同的说法并不完全正确:JSON 将数组定义为有序的值列表。
["Bryan", "John"]
不等于:
["John", "Bryan"]