web-dev-qa-db-ja.com

jqまたは代替コマンドラインツールを使用してJSONファイルを比較する

2つのJSONファイルが、辞書内のキーおよびリスト内の要素の順序に不変で同一であるかどうかを確認するために使用できるコマンドラインユーティリティはありますか?

これは jq または他の同等のツールで実行できますか?

例:

これら2つのJSONファイルは同一です

A

{
  "People": ["John", "Bryan"],
  "City": "Boston",
  "State": "MA"
}

B

{
  "People": ["Bryan", "John"],
  "State": "MA",
  "City": "Boston"
}

ただし、これら2つの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
56

Jqの比較では、キーの順序を考慮せずにオブジェクトを既に比較しているため、残っているのは、比較する前にオブジェクト内のすべてのリストを並べ替えることだけです。 2つのファイルの名前が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'

このプログラムは、要求する平等の定義を使用して、オブジェクトが等しいかどうかに応じて、「true」または「false」を返す必要があります。

編集:(.. | arrays) |= sortコンストラクトは、実際には一部のEdgeケースで期待どおりに機能しません。 この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'
22
user3899165

原則として、bashまたは他の高度なシェルにアクセスできる場合、次のようなことができます。

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

サブプロセスを使用します。これは、ソートされたキーと、浮動小数点の一貫した表現でJSONをフォーマットします。これらが、同じコンテンツのjsonが異なる方法で印刷される理由について考えられる唯一の2つの理由です。したがって、後で単純な文字列比較を実行すると、適切なテストが行​​われます。また、bashを使用できない場合、一時ファイルでも同じ結果を得ることができることに注意してください。

これは、あなたが質問を述べた方法で["John", "Bryan"]["Bryan", "John"]を同じように比較したかったので、あなたの質問にはまったく答えません。 jsonにはセットの概念はなく、リストのみがあるため、これらは別個のものと見なす必要があります。リストにとって順序は重要です。同等に比較したい場合は、カスタム比較を作成する必要があります。そのためには、平等の意味を定義する必要があります。すべてのリストまたは一部のリストの順序は重要ですか?重複する要素はどうですか?あるいは、それらをセットとして表し、要素が文字列である場合は、{"John": null, "Bryan": null}などのオブジェクトに配置できます。等しいかどうかを比較する場合、順序は関係ありません。

更新

コメントのディスカッションから:jsonが同じではない理由をよりよく知りたい場合は、

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

より解釈可能な出力を生成します。 vimdiffは好みに応じてdiffよりも望ましい場合があります。

61
Erik

jd-setオプションとともに使用します。

出力がない場合、差はありません。

$ jd -set A.json B.json

違いは@パスと+または-として表示されます。

$ jd -set A.json C.json

@ ["People",{}]
+ "Carla"

出力diffは、-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

11
Joe Burnett

以下は、汎用関数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を使用するバージョンを使用する必要があります。

6
peak

おそらく、この並べ替えと差分ツールを使用できます: http://novicelab.org/jsonsortdiff/ 最初にオブジェクトを意味的に並べ替えてから比較します。これは https://www.npmjs.com/package/jsonabc に基づいています

1
Shivraj

インスピレーションとして@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"
 }]
0
tokland

jqベースのjson diffを取得するために、上位2つの回答からベストを引き出します。

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 (配列をセットとして処理できるようにする)とdiffへのクリーンなbashリダイレクトからエレガントな配列ソートソリューションを取得します。 from https://stackoverflow.com/a/37175540/538507 これは、2つのjsonファイルの差分が必要な場合に対応し、配列の内容の順序は関係ありません。

0
Andrew