是否有任何命令行实用程序可用于查找两个JSON文件是否与in-dictionary-key和within-list-element排序的不变性相同?
这可以用jq其他等效工具完成吗?
这两个JSON文件是相同的
A:
{
"People": ["John", "Bryan"],
"City": "Boston",
"State": "MA"
}
Run Code Online (Sandbox Code Playgroud)
B:
{
"People": ["Bryan", "John"],
"State": "MA",
"City": "Boston"
}
Run Code Online (Sandbox Code Playgroud)
但是这两个JSON文件是不同的:
A:
{
"People": ["John", "Bryan", "Carla"],
"City": "Boston",
"State": "MA"
}
Run Code Online (Sandbox Code Playgroud)
C:
{
"People": ["Bryan", "John"],
"State": "MA",
"City": "Boston"
}
Run Code Online (Sandbox Code Playgroud)
那将是:
$ some_diff_command A.json B.json
$ some_diff_command A.json C.json
The files are not structurally identical
Run Code Online (Sandbox Code Playgroud)
Eri*_*rik 68
原则上,如果你有权访问bash或其他一些高级shell,你可以做类似的事情
cmp <(jq -cS . A.json) <(jq -cS . B.json)
Run Code Online (Sandbox Code Playgroud)
使用子进程.这将使用排序键和浮点的一致表示格式化json.这些是我能想到的两个原因,即为什么具有相同内容的json将以不同方式打印.因此,之后进行简单的字符串比较将导致适当的测试.可能还值得注意的是,如果你不能使用bash,你可以使用临时文件得到相同的结果,它只是不那么干净.
这并没有完全回答你的问题,因为在你说出你想要的问题的方式["John", "Bryan"]和["Bryan", "John"]相同的比较.由于json不具有集合的概念,只有列表,因此应将其视为不同的.订单对于列表很重要.如果您希望它们进行相同的比较,您将不得不编写一些自定义比较,为此,您需要通过相等来定义您的意思.订单是否适合所有列表或只有一些?重复元素怎么样?或者,如果您希望它们被表示为一个集合,并且元素是字符串,您可以将它们放在像这样的对象中{"John": null, "Bryan": null}.在比较平等时,顺序无关紧要.
从评论讨论:如果你想更好地了解为什么json不一样,那么
diff <(jq -S . A.json) <(jq -S . B.json)
Run Code Online (Sandbox Code Playgroud)
将产生更多可解释的输出.vimdiff根据口味可能比较差异.
小智 27
由于jq的比较已经比较了对象而没有考虑键排序,所以剩下的就是在比较对象之前对对象内的所有列表进行排序.假设你的两个文件被命名为a.json和b.json,在夜间最新JQ:
jq --argfile a a.json --argfile b b.json -n '($a | (.. | arrays) |= sort) as $a | ($b | (.. | arrays) |= sort) as $b | $a == $b'
Run Code Online (Sandbox Code Playgroud)
该程序应返回"true"或"false",具体取决于对象是否相等,使用您要求的相等定义.
编辑:(.. | arrays) |= sort在某些边缘情况下,构造实际上并不像预期的那样工作.这个GitHub问题解释了为什么并提供了一些替代方案,例如:
def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); (post_recurse | arrays) |= sort
Run Code Online (Sandbox Code Playgroud)
应用于上面的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'
Run Code Online (Sandbox Code Playgroud)
Joe*_*ett 13
使用jd与-set选项:
没有输出意味着没有区别.
$ jd -set A.json B.json
Run Code Online (Sandbox Code Playgroud)
差异显示为@路径和+或 - .
$ jd -set A.json C.json
@ ["People",{}]
+ "Carla"
Run Code Online (Sandbox Code Playgroud)
输出差异也可以用作带有-p选项的补丁文件.
$ jd -set -o patch A.json C.json; jd -set -p patch B.json
{"City":"Boston","People":["John","Carla","Bryan"],"State":"MA"}
Run Code Online (Sandbox Code Playgroud)
https://github.com/josephburnett/jd#command-line-usage
And*_*rew 13
从前两个答案中提取最佳答案以获得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")
Run Code Online (Sandbox Code Playgroud)
这采用了/sf/answers/2235326411/中优雅的数组排序解决方案(它允许我们将数组视为集合)以及/sf/answers/2602287831/diff中的干净的 bash 重定向。 538507这解决了您想要比较两个 json 文件且数组内容的顺序不相关的情况。
这是使用通用函数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);
Run Code Online (Sandbox Code Playgroud)
例:
{"a":[1,2,[3,4]]} | equiv( {"a": [[4,3], 2,1]} )
Run Code Online (Sandbox Code Playgroud)
生产:
true
Run Code Online (Sandbox Code Playgroud)
并作为一个bash脚本包装:
#!/bin/bash
JQ=/usr/local/bin/jq
BN=$(basename $0)
function help {
cat <<EOF
Syntax: $0 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 "$1" = -h -o "$1" = --help ] ; then help ; exit ; fi
test -f "$1" || die "unable to find $1"
test -f "$2" || die "unable to find $2"
$JQ -r -n --argfile A "$1" --argfile B "$2" -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
)
Run Code Online (Sandbox Code Playgroud)
POSTSCRIPT:walk/1是jq> 1.5版本的内置版本,因此如果你的jq包含它,可以省略它,但是在jq脚本中冗余地包含它是没有害处的.
POST-POSTSCRIPT:walk最近更改了内置版本,因此它不再对对象中的键进行排序.具体来说,它使用keys_unsorted.对于手头的任务,keys应该使用版本.