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

时间:2015-08-10 22:11:25

标签: json diff jq

是否有任何命令行实用程序可用于查找两个JSON文件是否与in-dictionary-key和within-list-element排序的不变性相同?

可以使用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

9 个答案:

答案 0 :(得分:56)

原则上,如果您可以访问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可能比较差异。

答案 1 :(得分:21)

由于jq的比较已经比较了对象而没有考虑键排序,所以剩下的就是在比较对象之前对对象内的所有列表进行排序。假设您的两个文件名为a.jsonb.json,请在最晚的jq上注明:

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

该程序应返回&#34; true&#34;或&#34;假&#34;根据你要求的平等定义,对象是否相等。

编辑:(.. | 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'

答案 2 :(得分:10)

jd-set选项一起使用:

没有输出意味着没有区别。

$ jd -set A.json B.json

差异显示为@ path和+或 - 。

$ 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

答案 3 :(得分: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&gt;版本的内置版本。 1.5,如果你的jq包含它,可以省略,但是在jq脚本中冗余地包含它没有任何害处。

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

答案 4 :(得分:2)

对此here有一个有用的答案。

基本上,您可以使用Git diff功能(即使对于非Git跟踪的文件),该功能还可以在输出中包括颜色:

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

答案 5 :(得分:1)

也许您可以使用以下排序和差异工具:http://novicelab.org/jsonsortdiff/,该工具首先对对象进行语义排序,然后进行比较。它基于https://www.npmjs.com/package/jsonabc

答案 6 :(得分:0)

如果您还希望看到差异,请使用@ Erik的答案作为灵感来源js-beautify

ListView

答案 7 :(得分:0)

从前两个答案中最好地提取出一个基于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重定向从https://stackoverflow.com/a/37175540/538507转变为diff,解决了您需要的情况两个json文件的差异,数组内容的顺序无关。

答案 8 :(得分:0)

对于那些以前的答案不太适合的人,您可以尝试使用jdd

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