使用jq或备用命令行工具比较JSON文件


78

是否有任何命令行实用程序可用于查找两个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

Answers:


36

由于jq的比较已经在不考虑键顺序的情况下比较了对象,因此剩下的就是在比较对象之前对对象中的所有列表进行排序。假设您的两个文件在每晚的最新jq中分别命名为a.jsonb.json

jq --argfile a a.json --argfile b b.json -n '($a | (.. | arrays) |= sort) as $a | ($b | (.. | arrays) |= sort) as $b | $a == $b'

该程序应使用您要求的相等性定义取决于对象是否相等,返回“ 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

应用于上面的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'

1
我一直试图改变--argfile a a.json--arg a $a(即$ AA JSON字符串),没有运气。任何想法如何处理字符串,而不是文件?
Simon Ernesto Cardenas Zarate

@SimonErnestoCardenasZarate如果您仍然遇到此问题,则可能需要使用该--argjson参数
Brian

91

原则上,如果您可以使用bash或其他高级Shell,则可以执行以下操作

cmp <(jq -cS . A.json) <(jq -cS . B.json)

使用子流程。这将使用排序的键和一致的浮点表示形式格式化json。这是我能想到的为什么只有两个内容相同的json会以不同的方式打印的两个原因。因此,之后进行简单的字符串比较将导致正确的测试。可能还值得注意的是,如果您不能使用bash,则可以通过临时文件获得相同的结果,但它不是那么干净。

这并不能完全回答您的问题,因为您以自己的方式陈述了您想要的问题["John", "Bryan"]["Bryan", "John"]进行了比较。由于json没有集合的概念,只有列表,因此应该将它们视为不同的。顺序对于列表很重要。如果您希望它们进行均等比较,则必须编写一些自定义比较,并且需要定义相等性的含义。顺序对所有列表或仅对某些列表都重要吗?重复元素呢?另外,如果您希望将它们表示为一个集合,并且元素是字符串,则可以将它们放在类似的对象中{"John": null, "Bryan": null}。比较平等性时顺序并不重要。

更新资料

从评论讨论中:如果您想更好地了解json为什么不相同的原因,那么

diff <(jq -S . A.json) <(jq -S . B.json)

将产生更多可解释的输出。vimdiff根据口味可能比diff更可取。


1
请注意,这似乎需要1.5版或更高版本jq
Adam Baxter

1
@voltagex通过查看在线手册(stedolan.github.io/jq/manual/v1.4/#Invokingjq),它似乎实际上是在1.4中添加的,尽管我不知道jqposix样式参数是否可以,所以您可能必须调用jq -c -S ...
Erik

4
IMO的外观更干净,更直观vimdiff <(jq -S . a.json) <(jq -S . b.json)
Ashwin Jayaprakash

1
是的,您应该删除-c(使输出紧凑),样式首选项与您的答案无关。
odinho-Velmont '17

@ odinho-Velmont @Ashwin Jayaprakash的确确实c不是严格必需的,但是对我而言,没有理由让cmp比较相同的空白,也没有理由让jq费心地发出它。diff,,vimdiff或进行文件比较的任何工具都可以使用,但这cmp是必需的。
Erik

19

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"}

https://github.com/josephburnett/jd#command-line-usage


因此低估它应该是轻罪。提供实际的diff格式兼容输出。惊人。
ijoseph

7

有这个答案在这里,将是有益的。

本质上,您可以使用Gitdiff功能(即使对于非Git跟踪文件也是如此),该功能还在输出中包括颜色:

git diff --no-index payload_1.json payload_2.json


2
这对命令很敏感,OP希望忽略该命令
Andreas

6

这是使用通用功能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 $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
)

POSTSCRIPT:walk / 1是jq> 1.5的内置版本,因此,如果您的jq包含walk / 1,则可以将其忽略,但是在jq脚本中多余地包含它没有害处。

POST-POSTSCRIPT:的内置版本walk最近已更改,因此它不再对对象内的键进行排序。具体来说,它使用keys_unsorted。对于手头的任务,keys应使用using版本。


1
感谢您提及walkjq 1.5中添加的内容。我一直希望在filter和之间有一个折衷运算符map,看起来就是这样。
诺亚·萨斯曼


1

从最上面的两个答案中提取最好的一个,以获得一个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")

这需要优雅的数组排序从溶液https://stackoverflow.com/a/31933234/538507(它允许我们治疗数组作为套)和清洁的bash重定向到diffhttps://stackoverflow.com/a/37175540/ 538507这解决了您需要两个json文件的差异且数组内容的顺序不相关的情况。


0

如果您还想查看差异,请使用@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"
 }]

6
......你知道或只是删除-cjq命令行。我不知道,宁愿不引入额外的不必要的工具;)
odinho-Velmont

0

对于以前答案不太合适的工具,您可以尝试jdd

它基于HTML,因此您可以在www.jsondiff.com上在线使用它,或者,如果您希望在本地运行它,只需下载项目并打开index.html。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.