json diff jq

Usar jq o herramientas de línea de comando alternativas para comparar archivos JSON



diff (7)

Aquí hay una solución que usa la función genérica 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);

Ejemplo:

{"a":[1,2,[3,4]]} | equiv( {"a": [[4,3], 2,1]} )

produce:

true

Y envuelto como un script 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 es una versión incorporada de jq> 1.5 y, por lo tanto, puede omitirse si su jq lo incluye, pero no hay ningún daño en incluirlo de forma redundante en un script jq.

POST-POSTSCRIPT: la versión incorporada de walk se ha cambiado recientemente para que ya no clasifique las claves dentro de un objeto. Específicamente, utiliza keys_unsorted . Para la tarea en cuestión, se debe usar la versión que utiliza keys .

¿Hay alguna utilidad de línea de comando que pueda usarse para encontrar si dos archivos JSON son idénticos con invariancia al orden dentro de la clave del diccionario y dentro de la lista de elementos?

¿Podría hacerse esto con jq o alguna otra herramienta equivalente?

Ejemplos:

Estos dos archivos JSON son idénticos.

A :

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

B :

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

pero estos dos archivos JSON son diferentes:

A :

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

C :

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

Eso sería:

$ some_diff_command A.json B.json $ some_diff_command A.json C.json The files are not structurally identical


Dado que la comparación de jq ya compara objetos sin tener en cuenta el orden de las claves, todo lo que queda es ordenar todas las listas dentro del objeto antes de compararlas. Suponiendo que sus dos archivos se a.json y b.json , en la última noche de jq:

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

Este programa debe devolver "verdadero" o "falso" dependiendo de si los objetos son iguales o no utilizando la definición de igualdad que solicita.

EDITAR: La construcción (.. | arrays) |= sort no funciona realmente como se esperaba en algunos casos extremos. Este problema de GitHub explica por qué y ofrece algunas alternativas, como:

def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); (post_recurse | arrays) |= sort

Aplicado a la invocación jq anterior:

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''


En principio, si tiene acceso a bash o algún otro shell avanzado, podría hacer algo como

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

utilizando subprocesos. Esto formateará el json con claves ordenadas y una representación consistente de puntos flotantes. Esas son las dos únicas razones por las que puedo pensar por qué json con el mismo contenido se imprimiría de manera diferente. Por lo tanto, hacer una comparación de cadena simple después dará como resultado una prueba adecuada. Probablemente también valga la pena señalar que si no puede usar bash puede obtener los mismos resultados con archivos temporales, simplemente no es tan limpio.

Esto no responde a su pregunta, porque en la forma en que dijo la pregunta que deseaba que ["John", "Bryan"] y ["Bryan", "John"] compararan idénticamente. Como json no tiene el concepto de un conjunto, solo una lista, deben considerarse distintos. El orden es importante para las listas. Tendría que escribir una comparación personalizada si quisiera que se compararan por igual, y para hacer eso necesitaría definir qué quiere decir con igualdad. ¿Importa el orden para todas las listas o solo para algunas? ¿Qué pasa con los elementos duplicados? Alternativamente, si desea que se representen como un conjunto, y los elementos son cadenas, puede colocarlos en objetos como {"John": null, "Bryan": null} . El orden no importará al comparar los de igualdad.

Actualizar

De la discusión de comentarios: si desea tener una mejor idea de por qué el json no es el mismo, entonces

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

producirá una salida más interpretable. vimdiff puede ser preferible a diff dependiendo de los gustos.



Sacando lo mejor de las dos respuestas principales para obtener un json diff basado en jq :

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

Esto toma la elegante solución de clasificación de matrices de https://.com/a/31933234/538507 (que nos permite tratar las matrices como conjuntos) y la redirección limpia de bash en diff desde https://.com/a/37175540 / 538507 Esto soluciona el caso en el que desea una diferencia de dos archivos json y el orden del contenido de la matriz no es relevante.


Si también quieres ver las diferencias, usa la respuesta de @ Erik como inspiración y 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" }]


Use jd con la opción -set :

Sin salida significa que no hay diferencia.

$ jd -set A.json B.json

Las diferencias se muestran como @ ruta y + o -.

$ jd -set A.json C.json @ ["People",{}] + "Carla"

Los diferenciales de salida también se pueden usar como archivos de parche con la opción -p .

$ jd -set -o patch A.json C.json; jd -set -p patch B.json {"City":"Boston","People":["John","Carla","Bryan"],"State":"MA"}

jd