Gibt es Befehlszeilen-Dienstprogramme, die verwendet werden können, um herauszufinden, ob zwei JSON-Dateien mit der Invarianz zu der Reihenfolge innerhalb des Dictionary-Schlüssels und innerhalb des Listenelements identisch sind?
Könnte dies mit jq
oder einem anderen gleichwertigen Werkzeug gemacht werden?
Diese beiden JSON-Dateien sind identisch
A
:
{
"People": ["John", "Bryan"],
"City": "Boston",
"State": "MA"
}
B
:
{
"People": ["Bryan", "John"],
"State": "MA",
"City": "Boston"
}
diese beiden JSON-Dateien unterscheiden sich jedoch:
A
:
{
"People": ["John", "Bryan", "Carla"],
"City": "Boston",
"State": "MA"
}
C
:
{
"People": ["Bryan", "John"],
"State": "MA",
"City": "Boston"
}
Das wäre:
$ some_diff_command A.json B.json
$ some_diff_command A.json C.json
The files are not structurally identical
Da der Vergleich von jq bereits Objekte vergleicht, ohne die Schlüsselreihenfolge zu berücksichtigen, müssen Sie lediglich alle Listen innerhalb des Objekts sortieren, bevor Sie sie vergleichen. Angenommen, Ihre beiden Dateien heißen a.json
und b.json
in der letzten nächtlichen jq:
jq --argfile a a.json --argfile b b.json -n '($a | (.. | arrays) |= sort) as $a | ($b | (.. | arrays) |= sort) as $b | $a == $b'
Dieses Programm sollte "true" oder "false" zurückgeben, abhängig davon, ob die Objekte mit der Definition der Gleichheit, nach der Sie fragen, gleich sind oder nicht.
BEARBEITEN: Das (.. | arrays) |= sort
-Konstrukt funktioniert in einigen Edge-Fällen nicht wie erwartet. Diese GitHub-Ausgabe erklärt warum und bietet einige Alternativen, wie:
def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); (post_recurse | arrays) |= sort
Auf den jq-Aufruf oben angewendet:
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'
Wenn Sie Zugriff auf Bash oder eine andere erweiterte Shell haben, können Sie im Prinzip Folgendes tun
cmp <(jq -cS . A.json) <(jq -cS . B.json)
mit Unterprozessen. Dadurch wird der Json mit sortierten Schlüsseln und einer konsistenten Darstellung von Fließpunkten formatiert. Dies sind die einzigen zwei Gründe, aus denen ich mir vorstellen kann, warum json mit dem gleichen Inhalt anders gedruckt wird. Wenn Sie anschließend einen einfachen String-Vergleich durchführen, wird ein ordnungsgemäßer Test durchgeführt. Es ist wahrscheinlich auch erwähnenswert, dass, wenn Sie bash nicht verwenden können, die gleichen Ergebnisse mit temporären Dateien erzielt werden können, es einfach nicht so sauber ist.
Dies beantwortet Ihre Frage nicht ganz, da Sie in der Art und Weise, in der Sie die Frage angegeben haben, dass ["John", "Bryan"]
und ["Bryan", "John"]
identisch verglichen werden sollen. Da json nicht das Konzept einer Menge hat, sondern nur eine Liste, sollten diese als gesondert betrachtet werden. Reihenfolge ist wichtig für Listen. Sie müssten einen benutzerdefinierten Vergleich schreiben, wenn Sie möchten, dass sie gleich miteinander verglichen werden. Um dies zu tun, müssen Sie definieren, was Sie unter Gleichheit verstehen. Ist die Reihenfolge für alle Listen oder nur für einige wichtig? Was ist mit doppelten Elementen? Wenn Sie möchten, dass sie als Satz dargestellt werden und die Elemente Strings sind, können Sie sie in Objekte wie {"John": null, "Bryan": null}
einfügen. Beim Vergleich der Gleichheit spielt die Ordnung keine Rolle.
Aus der Kommentardiskussion: Wenn Sie eine bessere Vorstellung davon bekommen wollen, warum der Json nicht derselbe ist, dann
diff <(jq -S . A.json) <(jq -S . B.json)
erzeugt eine interpretierbarere Ausgabe. vimdiff
kann je nach Geschmack vorzuziehen sein.
Hier ist eine Lösung, die die generische Funktion walk/1 verwendet:
# 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);
Beispiel:
{"a":[1,2,[3,4]]} | equiv( {"a": [[4,3], 2,1]} )
produziert:
true
Und als Bash-Skript verpackt:
#!/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: [email protected]" >&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 ist eine integrierte Version von jq> 1.5 und kann daher weggelassen werden, wenn Ihr jq es enthält. Es ist jedoch kein Nachteil, wenn Sie es redundant in ein jq-Skript einfügen.
POST-POSTSCRIPT: Die integrierte Version von walk
wurde kürzlich geändert, so dass die Schlüssel innerhalb eines Objekts nicht mehr sortiert werden. Insbesondere wird keys_unsorted
verwendet. Für die vorliegende Aufgabe sollte die Version verwendet werden, die keys
verwendet.
Verwenden Sie jd
mit der Option -set
:
Keine Ausgabe bedeutet keine Differenz.
$ jd -set A.json B.json
Unterschiede werden als @ Pfad und + oder - angezeigt.
$ jd -set A.json C.json
@ ["People",{}]
+ "Carla"
Die Ausgabedifferenzen können mit der Option -p
auch als Patch-Dateien verwendet werden.
$ jd -set -o patch A.json C.json; jd -set -p patch B.json
{"City":"Boston","People":["John","Carla","Bryan"],"State":"MA"}
Vielleicht könnten Sie dieses Sorten- und Diff-Werkzeug verwenden: http://novicelab.org/jsonsortdiff/ , das die Objekte zunächst semantisch sortiert und dann vergleicht. Es basiert auf https://www.npmjs.com/package/jsonabc
Wenn Sie auch die Unterschiede sehen möchten, verwenden Sie @ Eriks Antwort als Inspiration und 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"
}]
Das Beste aus den beiden obersten Antworten ziehen, um ein jq
-basiertes JSON-Diff zu erhalten:
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")
Dies übernimmt die elegante Array-Sortierlösung von https://stackoverflow.com/a/31933234/538507 (die es uns ermöglicht, Arrays als Mengen zu behandeln) und die saubere Bash-Umleitung in diff
from https://stackoverflow.com/a/37175540/538507 Dies betrifft den Fall, dass Sie einen Unterschied von zwei JSON-Dateien wünschen und die Reihenfolge des Array-Inhalts nicht relevant ist.