Scegliere uno strumento di diff JSON: testo, struttura e visualizzazione
In sintesi: Il diff testuale trova dove i file differiscono; il diff strutturale decide se i contenuti sono equivalenti. Scegliere il livello giusto evita di scambiare l'ordine delle chiavi per un incidente.

In sintesi: il diff testuale scopre il cambiamento (ordina prima le chiavi con jq -S), il diff strutturale decide l'equivalenza (allinea gli array per chiave primaria), e la visualizzazione si limita a spiegarlo o a rivederlo; mantieni separati i tre livelli in CI.
Due documenti JSON che sembrano diversi non significa che i loro contenuti siano diversi. Scegli il livello di confronto giusto prima di scegliere lo strumento.
1. Tre tipi di "diff" non sono la stessa cosa
Molti team trattano "confrontare JSON" come un'unica azione, e poi incontrano gli stessi problemi in scenari diversi. Conviene separare tre livelli:
| Livello | Cosa viene confrontato | Cosa rivela | Uso tipico |
|---|---|---|---|
| Diff testuale | Byte e righe grezze | Indentazione, ordine delle chiavi, spazi, a capo | Drift della configurazione, code review |
| Diff strutturale | Oggetti e array analizzati | Chiavi aggiunte o rimosse, valori cambiati, modifiche agli elementi degli array | Regressioni delle API, migrazioni di dati |
| Diff visuale | Come vengono presentate le differenze | A quale livello appartiene un cambiamento, quanto grande è il suo raggio d'azione | Revisione umana, discussione di squadra |
Il diff testuale è senza dipendenze, veloce e si integra naturalmente con Git. Il suo punto debole è che non sa nulla della semantica di JSON. Due documenti completamente equivalenti si illuminano di rosso e verde se l'ordine delle chiavi o l'indentazione differiscono. Il diff strutturale è l'opposto: analizza prima e poi confronta, quindi riconosce che un valore si è semplicemente spostato. Ma ha bisogno della strategia giusta di allineamento degli array; altrimenti un singolo inserimento viene letto come una riscrittura completa.
La visualizzazione non è un terzo algoritmo. Renderizza l'output degli altri due livelli così che una persona possa confermare rapidamente se la differenza è prevista. È buona per il giudizio umano e scarsa come sostituto dei controlli automatici. Mescolare i livelli di solito produce una pipeline CI permanentemente rossa e un team che ha imparato a ignorarla.
2. Diff testuale: ripulisci prima l'ordine delle chiavi con jq -S
Per una rapida occhiata a due file, la combinazione più economica è jq -S più diff -u. L'opzione -S ordina ricorsivamente le chiavi degli oggetti, normalizzando documenti il cui contenuto è identico ma l'ordine delle chiavi differisce:
jq -S . a.json > a.sorted.json
jq -S . b.json > b.sorted.json
diff -u a.sorted.json b.sorted.json
Questo elimina il falso positivo più comune. RFC 8259 definisce gli oggetti come collezioni non ordinate, quindi {"a":1,"b":2} e {"b":2,"a":1} sono lo stesso valore. Un diff testuale, però, marca ogni riga come cambiata. Ordinare prima avvicina molto i blocchi rossi ai problemi reali.
Nota che jq -S ordina solo le chiavi; non ordina gli array. L'ordine degli array è significativo in JSON, e [1,2] non è lo stesso valore di [2,1], quindi lo strumento non prenderà quella decisione per te. Se un array è concettualmente un insieme non ordinato, ordinalo tu per chiave primaria prima di confrontare.
C'è un altro dettaglio da ricordare: diff esce con codice 1 quando trova differenze, ma un file mancante o un errore di permessi produce anch'esso un'uscita diversa da zero. In CI, non trattare ogni codice diverso da zero come "contenuto cambiato", altrimenti un percorso digitato male verrà segnalato come cambiamento dei dati. Uno schema più sicuro verifica prima i file e poi gestisce la differenza:
if ! diff -u a.sorted.json b.sorted.json; then
echo "JSON content differs" >&2
exit 1
fi
delta rende leggibile il diff testuale
L'output grezzo di diff -u funziona, ma le righe lunghe e il JSON profondamente annidato stancano da leggere. delta è un filtro di diff che aggiunge evidenziazione della sintassi, numeri di riga e colori di sfondo più chiari:
diff -u a.sorted.json b.sorted.json | delta
È nato come pager di Git, quindi può anche sostituire git diff. Per chi legge dieci diff di configurazione al giorno, strumenti come questo migliorano la velocità di lettura più che l'accuratezza dell'algoritmo. Un'altra scelta comune è git diff --no-index --word-diff, che confronta a livello di parola e funziona bene per il JSON compresso memorizzato su righe molto lunghe.
Un avvertimento: abbellire non elimina le differenze false. Se l'ordine delle chiavi non è stato normalizzato, delta rende semplicemente il rumore più carino. L'ordine corretto è sempre prima normalizzare, poi decorare.
3. Diff strutturale: jsondiffpatch comprende la semantica
Quando la domanda è se due pezzi di dati sono semanticamente equivalenti, usa un diff strutturale. L'opzione più comune nell'ecosistema JavaScript è jsondiffpatch, che restituisce un oggetto delta che descrive i cambiamenti invece di righe di testo con marcatori + e -:
import { create } from 'jsondiffpatch';
const instance = create();
const delta = instance.diff(
{ id: 1, tags: ['a', 'b'], enabled: true },
{ id: 1, tags: ['a', 'c'], enabled: false },
);
console.log(JSON.stringify(delta, null, 2));
Il delta distingue chiavi aggiunte, chiavi rimosse, valori sostituiti ed elementi di array spostati. Questo significa che puoi percorrere il delta direttamente nelle asserzioni: undefined quando non c'è differenza, oppure una descrizione percorso per percorso quando c'è. A differenza di un diff testuale, non è disturbato da indentazione, ordine delle chiavi o grafia dei numeri come 1e3 contro 1000.
Altrettanto importante, l'output di un diff strutturale può alimentare un flusso di patching. jsondiffpatch fornisce patch e reverse, quindi un delta può essere applicato all'oggetto originale e annullato in seguito. Un avvertimento: un delta generato automaticamente non ha lo stesso formato di un JSON Patch scritto a mano, e convertirli richiede lavoro. Per l'approccio più ampio al confronto, vedi Diff e confronto JSON.
4. Come eliminare le differenze false causate dall'ordine delle chiavi
L'ordine delle chiavi non è solo fastidioso; può produrre conclusioni sbagliate. Segui tre passaggi durante l'indagine:
- Prima di confrontare, normalizza ricorsivamente l'ordine delle chiavi con
jq -So uno strumento equivalente. - Rimuovi i campi che cambiano a ogni esecuzione, come
timestamp,requestIdetraceId. - Conferma che non ci siano chiavi duplicate. Le chiavi duplicate vengono risolte con "vince l'ultima" durante il parsing, quindi un diff testuale le vede mentre un diff strutturale no.
jq -S 'del(.meta.timestamp, .meta.requestId)' a.json > /tmp/a.json
jq -S 'del(.meta.timestamp, .meta.requestId)' b.json > /tmp/b.json
diff -u /tmp/a.json /tmp/b.json
Questo flusso si adatta bene al drift della configurazione. Passa staging e produzione attraverso la stessa normalizzazione, e ciò che resta è la differenza che richiede davvero una spiegazione. Mantieni le regole di filtro in uno script versionato invece di ribatterle a memoria. Per il lato modifica e scripting della toolchain, vedi Editor, riga di comando e automazione.
5. Array: allineare per indice o per id?
Gli array sono la parte più soggetta a errori nel confronto JSON. Con l'allineamento predefinito per indice, inserire un record all'inizio sposta ogni elemento successivo, e lo strumento li marca tutti come cambiati. Lo strumento non è rotto; la strategia di allineamento è sbagliata.
| Allineamento | Funziona quando | Punto di forza | Rischio |
|---|---|---|---|
| Per indice | L'ordine dell'array ha significato di business | Semplice, non servono campi extra | Un inserimento causa falsi positivi a cascata |
| Per chiave primaria | Gli elementi sono un insieme con un id unico | Rileva inserimenti, eliminazioni e spostamenti | Richiede una chiave stabile e unica |
| Sottosequenza comune più lunga | L'ordine conta ma gli inserimenti sono ammessi | Più robusto del semplice indicizzazione | Più difficile da spiegare, costo maggiore |
jsondiffpatch usa objectHash per dichiarare una chiave primaria, indicizzando gli elementi degli array prima del confronto:
const instance = create({
objectHash: (item: { id?: string; sku?: string }) => item.id ?? item.sku,
});
Con jq semplice, puoi ordinare prima e poi usare un diff testuale:
jq -S 'sort_by(.id)' items.json > items.sorted.json
Il compromesso dell'allineamento per id è che i cambiamenti di posizione non contano più come differenze. Se il business deve sapere che un record si è spostato dalla posizione 2 alla 5, confronta anche un campo ordine esplicito; non affidarti solo al contenuto dell'insieme.
6. Diff visuale: il livello costruito per le persone
L'output di un diff strutturale è progettato per le macchine, non per le riunioni. Quando devi spiegare un cambiamento a qualcun altro, la visualizzazione è più efficace:
- Il diff editor di VS Code è buono per la revisione file per file, con pannelli affiancati ed evidenziazioni in linea.
- Visualizzatori ad albero come JSON Crack e JSON Hero mostrano a quale livello appartiene un cambiamento.
jsondiffpatchinclude un formattatore HTML che renderizza un delta come vista ricca comprimibile.
Il valore della visualizzazione è il contesto. Un diff testuale dice che è cambiata la riga 88; una vista ad albero dice che la riga 88 appartiene a data.items[3].price. Ma la visualizzazione di solito carica l'intero documento in un browser o in un'app desktop. Prima di aprire un payload di produzione, conferma se lo strumento calcola localmente o carica su un server.
7. Tre pratiche per mettere il diff in CI
Primo, normalizza prima di confrontare. Fissa l'indentazione, ordina ricorsivamente le chiavi e filtra i campi volatili prima che parta il diff. Altrimenti la CI fallisce ripetutamente per rumore.
Secondo, scegli l'allineamento in base alla semantica. Se un array è un insieme, allinea per chiave primaria. Se è una sequenza, mantieni l'allineamento per indice. Non applicare una sola regola a ogni API.
Terzo, asserisci valori, non testo. Confrontare oggetti analizzati nei test è molto più stabile che confrontare l'output di JSON.stringify. Quando un controllo fallisce, stampa il percorso della differenza e i valori prima/dopo invece di scaricare l'intero payload nel log.
# Print only the differing paths and values, not the entire document
jq -S 'del(.timestamp)' actual.json > /tmp/actual.json
jq -S 'del(.timestamp)' expected.json > /tmp/expected.json
diff -u /tmp/expected.json /tmp/actual.json | head -n 80
Se una differenza richiede un monitoraggio a lungo termine, conferma insieme lo script di normalizzazione e il file atteso. Se è un'indagine una tantum, elimina lo script temporaneo in seguito perché non diventi silenziosamente una seconda implementazione della logica di business.
8. Tabella di scelta dello strumento
| Scenario | Prima scelta | Alternativa | Promemoria chiave |
|---|---|---|---|
| Confronto occasionale di file | Diff di VS Code | diff -u |
Formatta prima per rimuovere il rumore |
| Drift della configurazione | jq -S + diff |
delta |
Filtra i campi volatili |
| Regressione delle risposte API | jsondiffpatch |
deepdiff |
Allinea gli array per chiave primaria |
| Spiegare un cambiamento alla squadra | JSON Crack / JSON Hero | Formattatore HTML | Controlla se i dati vengono caricati |
| File molto grandi | Estrazione di sottalberi con jq |
Navigazione con jless |
Non caricare l'intero albero in memoria |
| Controllo pre-commit | git diff --no-index |
pre-commit | Gestisci esplicitamente i codici di uscita |
Non esiste un unico miglior strumento di diff JSON; esiste solo lo strumento giusto per il livello corrente. Il diff testuale scopre il cambiamento, il diff strutturale giudica l'equivalenza e la visualizzazione spiega il cambiamento. Mantenere separati i tre livelli è molto più affidabile che cercare un unico strumento che faccia tutto. Per la gestione dei conflitti tra branch, continua con JSON e merge di Git.
FAQ
jq -S cambia anche l'ordine degli array?
No. -S ordina solo le chiavi degli oggetti e lascia gli array nel loro ordine originale. Gli array sono ordinati in JSON, quindi lo strumento non trasformerà [1,2] in [2,1]. Se un array è concettualmente un insieme non ordinato, ordinalo esplicitamente con sort_by.
1 e 1.0 sono lo stesso valore in un diff strutturale?
La maggior parte dei parser li trasforma nello stesso numero, quindi un diff strutturale di solito li considera uguali, mentre un diff testuale li marca come diversi. Usa il diff strutturale per decidere l'equivalenza e il diff testuale per decidere se i file sono identici byte per byte.
L'output di un diff può essere usato direttamente come JSON Patch?
Non direttamente. Uno strumento di diff riporta ciò che differisce, mentre JSON Patch descrive un elenco ordinato di operazioni; formati e semantica differiscono. Le patch generate automaticamente tendono anche a fare riferimento a indici di array, che diventano obsoleti in caso di concorrenza. Aggiungi un'operazione test prima di applicarle.
Come dovrei confrontare file JSON di decine di megabyte?
Estrai prima il sottalbero che ti interessa, per esempio con jq '.data.items', poi ordinalo per chiave primaria e confronta. Non caricare l'intero albero in memoria per un diff ricorsivo. I file molto grandi si gestiscono meglio con confronti in streaming o a blocchi, e il costo in memoria richiede una valutazione separata.
Gli strumenti di diff online possono far trapelare i miei dati?
Dipende dall'implementazione. Gli strumenti puramente frontend calcolano nel browser e non caricano dati; gli strumenti renderizzati sul server inviano il contenuto a un backend, dove può finire nei log. Prima di confrontare configurazioni di produzione o payload di clienti, conferma che la pagina calcoli localmente, e se necessario prova una volta con la rete disconnessa.
Come decido tra un diff testuale e uno strutturale?
Per una rapida occhiata a dove due file differiscono, ordina le chiavi con jq -S ed esegui diff -u. Per decidere se due documenti sono semanticamente equivalenti, usa un diff strutturale come jsondiffpatch e allinea gli array per chiave primaria. Il diff testuale è senza dipendenze e si integra con Git, mentre il diff strutturale non è disturbato da indentazione, ordine delle chiavi o grafia dei numeri.
Perché la CI non dovrebbe trattare ogni codice di uscita diverso da zero di diff come una differenza?
diff esce con 1 quando trova differenze, ma un file mancante o un errore di permessi restituisce anch'esso un valore diverso da zero. Trattare ogni codice diverso da zero come "contenuto cambiato" segnala un percorso digitato male come cambiamento dei dati. Uno schema più sicuro verifica prima i file e poi gestisce separatamente la differenza.
Devo fare il diff di file di decine di megabyte nel browser?
No. Estrai prima con jq il sottalbero che ti interessa, poi ordinalo per chiave primaria e confronta. Caricare l'intero albero in memoria per un diff ricorsivo costa molta più memoria e tempo. I file molto grandi si gestiscono meglio con confronti in streaming o a blocchi.