Vai al contenuto
jsonbeautifiers
Italiano

Confrontare JSON

Confronta due documenti per struttura: riformattare o riordinare le chiavi non conta.

Originale
Modificato

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.