Confrontare JSON
Confronta due documenti per struttura: riformattare o riordinare le chiavi non conta.
Nulla di ciò che incolli lascia il tuo browser. L’allowlist connect-src ne fa una garanzia del browser anziché una promessa. Verificalo tu stesso
Questo confronta due documenti per struttura e non per riga. Riformatta uno dei due, ordina le sue chiavi, cambia l’indentazione: il diff resta vuoto, perché nulla di tutto ciò ha cambiato i dati.
Un diff testuale su JSON è quasi inutile esattamente per questo motivo. Riserializza un documento con un’altra libreria e ogni riga risulta modificata mentre in realtà non è cambiato nulla.
Che cosa conta come differenza
Quattro tipi di modifica, ciascuno segnalato con un JSON Pointer alla posizione esatta.
- Aggiunto
- Una chiave o un elemento presente a destra e assente a sinistra.
- Rimosso
- Presente a sinistra e assente a destra.
- Modificato
- Presente in entrambi con un valore diverso dello stesso tipo.
- Tipo cambiato
- Presente in entrambi con un tipo diverso. Vale la pena separarlo, perché un numero diventato stringa di solito è un bug di serializzazione e non un cambio di dati.
Come vengono confrontati gli array, e perché conta
Gli array sono la parte difficile di ogni diff strutturale, e non esiste un’unica risposta giusta.
Confrontare posizione per posizione è corretto per una tupla di forma fissa e catastrofico per una lista. Inserisci un elemento in testa a un array da mille elementi e ogni posizione successiva risulta modificata, il che seppellisce l’unica differenza vera sotto mille false.
Questo strumento esegue invece una passata di sottosequenza comune più lunga, così un inserimento viene segnalato come inserimento. Costa un tempo proporzionale al prodotto delle due lunghezze, quindi oltre una soglia ripiega sul confronto posizionale e lo dichiara nelle note. Non degrada mai in silenzio.
Cose che di proposito non sono differenze
- L’ordine delle chiavi
- Gli oggetti JSON non sono ordinati secondo la specifica. Due documenti con gli stessi membri in ordine diverso sono lo stesso documento.
- Spazi bianchi e indentazione
- Non significativi per un parser, quindi invisibili qui.
- 0 e -0
- Distinti secondo Object.is ma identici come numeri JSON.
How to do this in code
Confronto strutturale nel codice.
sh jq
La prima riga è quella da ricordare. Ordinare prima le chiavi toglie quasi tutto il rumore da un diff testuale.
# Sort keys recursively, then diff the text. Cheap and
# surprisingly effective for small documents.
diff <(jq -S . a.json) <(jq -S . b.json)
# Keys present in one and not the other
jq -n --slurpfile a a.json --slurpfile b b.json \
'($a[0] | paths) - ($b[0] | paths)' py Python
from deepdiff import DeepDiff
diff = DeepDiff(a, b, ignore_order=True)
print(diff)
# Standard library only, for a shallow comparison
changed = {k for k in a.keys() | b.keys() if a.get(k) != b.get(k)} js JavaScript
JSON Patch è il formato standard per trasmettere un diff JSON, e i suoi percorsi sono puntatori RFC 6901 come quelli che segnala questo strumento.
import { compare } from 'fast-json-patch';
// RFC 6902 JSON Patch: a list of operations that turns a into b
const patch = compare(a, b);
// [{ op: 'replace', path: '/user/name', value: 'Priya' }] go Go
go-cmp è la scelta standard nei test Go e produce un output leggibile per strutture annidate.
import "github.com/google/go-cmp/cmp"
if d := cmp.Diff(a, b); d != "" {
t.Errorf("mismatch (-want +got):\n%s", d)
} Domande frequenti
- Perché riordinare le chiavi non mostra alcuna differenza?
- Perché la RFC 8259 definisce un oggetto come una collezione non ordinata. In pratica ogni parser conserva l’ordine di inserimento, e i diff che ci contano stanno testando la serializzazione anziché i dati.
- Posso ignorare campi come i timestamp?
- Sì. Ignorare un percorso lo toglie del tutto dal confronto: è così che si confrontano due risposte di API che differiscono solo per requestId e generatedAt.
- Che cos’è un JSON Pointer?
- La RFC 6901: un percorso separato da barre come /users/0/email. Dentro una chiave due caratteri vanno escapati: ~ diventa ~0 e / diventa ~1. Ogni modifica segnalata qui ne porta uno, così puoi indirizzare la posizione da codice.