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

Posted

技术标签:

【中文标题】使用 jq 或替代命令行工具比较 JSON 文件【英文标题】:Using jq or alternative command line tools to compare JSON files 【发布时间】:2015-11-02 23:40:17 【问题描述】:

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

【问题讨论】:

【参考方案1】:

如果您的 shell 支持进程替换(以下是 Bash 样式,请参阅 docs):

diff <(jq --sort-keys . A.json) <(jq --sort-keys . B.json)

对象的键顺序将被忽略,但数组顺序仍然很重要。如果需要,可以通过以其他方式对数组值进行排序或使它们类似于集合(例如 ["foo", "bar"]"foo": null, "bar": null;这也将删除重复项)来解决此问题。

或者,将diff 替换为其他比较器,例如cmpcolordiffvimdiff,取决于您的需要。如果您只想回答是或否,请考虑使用 cmp 并将 --compact-output 传递给 jq 以不格式化输出,以实现潜在的小幅性能提升。

【讨论】:

请注意,这似乎需要 jq 的 1.5 或更高版本 @voltagex 查看在线手册 (stedolan.github.io/jq/manual/v1.4/#Invokingjq) 似乎它实际上是在 1.4 中添加的,虽然我不知道 jq 是否有 posix 样式参数所以你可能不得不调用jq -c -S ... IMO 更清晰、更直观的形式是 vimdiff &lt;(jq -S . a.json) &lt;(jq -S . b.json) 是的,您应该删除 -c(这会使输出紧凑),样式偏好与您的答案无关。 @odinho-Velmont @Ashwin Jayaprakash c 确实不是绝对必要的,但对我来说,cmp 没有理由比较相同的空格,jq 也没有理由费心发出它. diffvimdiff 或任何进行文件比较的工具都可以使用,但只需 cmp【参考方案2】:

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 格式兼容的输出。太棒了。 可以使用命令行工具,也可以使用网页工具:play.jd-tool.io 这是使用json(和yaml,转换后)配置的圣杯工具,看看为什么一个人的配置与其他人的配置相比不起作用。 我只为 Linux 构建。但既然你问了,我已经交叉编译了最新版本:github.com/josephburnett/jd/releases/tag/v1.4.0。下载 jd-amd64-darwin 应该可以在 OSX 上运行。 在 MacOS 上使用 Homebrew:brew install jd【参考方案3】:

由于 jq 的比较已经比较对象而不考虑键顺序,剩下的就是在比较对象之前对对象内的所有列表进行排序。假设你的两个文件分别命名为a.jsonb.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'

这个程序应该返回“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'

【讨论】:

我一直在尝试将 --argfile a a.json 更改为 --arg a $a(作为 $a json 字符串),但没有成功。知道如何处理字符串而不是文件吗? @SimonErnestoCardenasZarate 如果您仍然遇到此问题,您可能需要--argjson 参数【参考方案4】:

这是一个使用通用函数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 包含它,可以将其省略,但在 jq 脚本中冗余包含它并没有什么坏处。

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

【讨论】:

感谢您提到walk 是在 jq 1.5 中添加的。我一直希望在filtermap 之间有一个折衷运算符,看起来就是这样。【参考方案5】:

here 有一个有用的答案。

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

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

【讨论】:

这对顺序很敏感,OP 想忽略它【参考方案6】:

从前两个答案中提取最佳答案以获得基于 jq 的 json diff:

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://***.com/a/31933234/538507 的优雅数组排序解决方案(它允许我们将数组视为集合)和来自https://***.com/a/37175540/538507 的干净bash 重定向到diff 这解决了您想要两个json 文件的差异的情况并且数组内容的顺序不相关。

【讨论】:

【参考方案7】:

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

【讨论】:

【参考方案8】:

对于那些之前的答案不适合的人来说,还有一个工具,你可以试试jdd。

它是基于 html 的,因此您可以通过 www.jsondiff.com 在线使用它,或者,如果您更喜欢在本地运行它,只需下载项目并打开 index.html。

【讨论】:

【参考方案9】:

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

【讨论】:

... 或者您知道只需从 jq 命令行中删除 -c。我不知道,不想引入额外的不必要的工具;)【参考方案10】:

在JSONiq,你可以简单地使用deep-equal函数:

deep-equal(
  
    "People": ["John", "Bryan", "Carla"],
    "City": "Boston",
    "State": "MA"
  ,
  
    "People": ["Bryan", "John"],
    "State": "MA",
    "City": "Boston"
  
)

返回

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

【讨论】:

以上是关于使用 jq 或替代命令行工具比较 JSON 文件的主要内容,如果未能解决你的问题,请参考以下文章

jq:给力的命令行 JSON 处理工具

如何使用 jq (或其他替代方法)解析 JSON 字符串?

linux下命令行json工具: jq

使用 MediaInfo 命令行和 jq 从文件目录构建有效的 JSON 播放列表

一个JSON字符串和文件处理的命令行神器jq,windows和linux都可用

转帖Linux命令行操作json神器jq