Diff JSON

Confronta due documenti JSON e vedi cosa è stato aggiunto, rimosso o modificato. L’ordine delle chiavi, l’indentazione e la minificazione non contano.

Primo JSON
Secondo JSON
Risultato

Incolla JSON in entrambi i riquadri per confrontarli

Confrontare i dati, non il testo

Un confronto riga per riga risponde a una domanda sulla scrittura: quali righe di questo file non sono righe di quell’altro. È la domanda giusta per la prosa e per il codice sorgente, ed è quella sbagliata per JSON, perché un documento JSON non ha un unico modo corretto di essere scritto. Riordina i membri di un oggetto, cambia l’indentazione o minifica un lato, e ogni riga cambia mentre i dati non si muovono affatto. Questo strumento confronta ciò in cui i due documenti vengono analizzati, così che quanto riporta sia ciò che è davvero cambiato. Qui sotto c’è uno stesso documento scritto due volte: minificato sulla prima riga, poi formattato e con i membri in un altro ordine.

{"name":"checkout","port":8080,"tags":["a","b"]}

{
  "port": 8080,
  "tags": ["a", "b"],
  "name": "checkout"
}

Quei due non hanno in comune neppure una riga, e questo strumento non riporta fra loro nessuna differenza: gli stessi membri, gli stessi valori, lo stesso ordine dentro l’elenco. La conseguenza pratica è che non devi mai riformattare un documento perché un confronto funzioni — il che vorrebbe dire cambiare proprio la cosa che ti eri messo a confrontare.

Che cosa non conta mai come differenza

Quattro aspetti del modo in cui un documento è scritto vengono ignorati, e vengono ignorati senza condizioni. Non c’è alcun comando che li riaccenda, perché uno strumento capace di farlo sarebbe due strumenti con due significati da spiegare, e chi legge dovrebbe sapere quale dei due ha prodotto la risposta che ha davanti.

  • L’ordine in cui sono scritti i membri di un oggetto. {"a": 1, "b": 2} e {"b": 2, "a": 1} portano gli stessi dati, quindi un serializzatore che emette le chiavi in un ordine diverso a ogni esecuzione non compare mai qui come un cambiamento.
  • Gli spazi di qualunque genere — l’indentazione, gli a capo, lo spazio dopo i due punti. Un documento minificato e uno formattato che portano gli stessi dati sono lo stesso documento due volte.
  • Come è scritto un numero. 1e3 è 1000 e 1.50 è 1.5, perché un numero JSON ha un valore e non una grafia: due documenti che differiscono solo lì portano gli stessi numeri, e su di essi non viene riportato proprio nulla.
  • Come si esegue l’escape di un carattere dentro una stringa. I sei caratteri \u0041 e la lettera A sono la stessa stringa di un carattere.

Due cose che sembrerebbero appartenere a quell’elenco ne sono deliberatamente fuori. L’ordine degli elementi di un elenco è dato — [1, 2] e [2, 1] sono documenti diversi — e un membro il cui valore è null non è la stessa cosa di un membro che non c’è affatto, quindi l’uno contro l’altro viene riportato anziché passato sotto silenzio.

Aggiunto, rimosso, cambiato — e un cambio di tipo

Ogni riga del risultato è esattamente di uno di tre generi, e insieme coprono tutto ciò che il confronto può trovare. Prendi questi due documenti:

{"host":"api.example.com","port":8080,"debug":true}

{"host":"api.example.com","port":"8080","retries":3}
  • Cambiato in $['port']: il membro è in entrambi i documenti e il suo valore non è lo stesso, quindi vengono mostrati entrambi i valori. Questo è anche segnalato come cambio di tipo, perché 8080 è un numero e "8080" è una stringa.
  • Rimosso in $['debug']: il membro è nel primo documento e non nel secondo.
  • Aggiunto in $['retries']: il membro è nel secondo documento e non nel primo.

Il membro chiamato host è in entrambi con lo stesso valore, quindi non viene riportato affatto. Un cambio di tipo è una proprietà di un cambiamento e non un quarto genere di riga: è la deriva che uno schema nota e che una persona nel leggere due file di solito no, quindi viene segnalata dove accade invece di essere lasciata al tuo occhio.

Dove si trova una differenza, scritto come un percorso

Ogni riga nomina il punto in cui si trova, e lo nomina come un percorso dentro il documento anziché come un numero di riga — un numero di riga sarebbe un fatto sulla scrittura, che è proprio ciò che questo strumento ha appena finito di ignorare. I percorsi sono percorsi normalizzati della RFC 9535, l’unica grafia che questo sito usa ovunque debba essere annotata una posizione dentro JSON.

  • $ è il documento intero, $['port'] è il membro chiamato port che vi si trova, e $['hosts'][1] è l’elemento all’indice 1 dell’elenco sotto hosts. Un membro si scrive sempre fra parentesi quadre e apici, così che un nome contenente un punto, uno spazio o una parentesi propria non richiede nulla di speciale.
  • Un percorso è una query che puoi eseguire. Incollalo nel Tester JSONPath di questo sito insieme al documento a cui appartiene e seleziona esattamente quel nodo, che è il modo più rapido di leggere una differenza nel suo contorno.
  • È una query contro il documento in cui il nodo sta davvero: il percorso di un membro aggiunto gira sul secondo documento e quello di un membro rimosso sul primo. Qui niente nomina mai un nodo che non c’è.

Una riga cambiata può portare due percorsi, e in questo si intravede l’accoppiamento della sezione seguente: appena un elenco ha guadagnato o perso un elemento, lo stesso elemento si trova a un indice diverso da ciascun lato. Dove i due percorsi coincidono — e accade quasi sempre — ne viene mostrato uno solo.

Come vengono accoppiati due elenchi

Due oggetti si confrontano membro per membro, ed è semplice perché un membro ha un nome. Due elenchi non hanno nomi, solo posizioni, e confrontarli posizione per posizione è ciò che rende illeggibile la maggior parte degli esiti di confronto: inserisci un record in testa a un elenco lungo e niente è più dov’era, così che un confronto che legge la posizione come identità riporta l’intero elenco come diverso — vero in senso stretto e inutile in ogni altro. Perciò gli elementi vengono prima accoppiati, per ciò che sono e non per un campo che tu indichi, e viene riportato solo ciò che l’accoppiamento lascia fuori.

{"hosts": ["alpha", "beta", "gamma"]}

{"hosts": ["alpha", "staging", "beta", "gamma"]}

Quella è una riga sola: staging è stato aggiunto in $['hosts'][1]. Gli elementi chiamati beta e gamma sono scalati di uno e non vengono menzionati, perché spostarsi non è cambiare. È lo stesso accoppiamento a permettere che un singolo campo modificato dentro un elemento di un elenco venga riportato come un cambiamento a quel campo, e non come la sostituzione dell’intero elemento con un altro.

L’ordine conta ancora, e l’accoppiamento non finge il contrario: [1, 2] contro [2, 1] torna come un 1 rimosso in testa e un 1 aggiunto dopo il 2, che è quanto è accaduto. Due elenchi possono anche essere troppo diversi perché accoppiarli valga il suo costo, e oltre quel punto il confronto ripiega sull’andare posizione per posizione e lo dice in cima al risultato — così una risposta rumorosa risulta spiegata invece che misteriosa.

Il rapporto e l’albero

Lo stesso confronto è offerto in due rese, e passare dall’una all’altra non ricalcola nulla: l’albero è disegnato proprio a partire dall’elenco di differenze che il rapporto enumera, così che i due non possano essere in disaccordo su che cosa è cambiato né su quanto ce n’è.

  • Il rapporto risponde a che cosa è cambiato: una riga per differenza, ciascuna con il suo percorso, il suo genere e il suo valore su ciascun lato. È la vista da scorrere quando vuoi l’intero elenco davanti.
  • L’albero risponde a dove nel documento: i due documenti fusi in un solo prospetto, con aperte soltanto le parti che contengono una differenza. Ogni tratto di figli consecutivi che non contengono nulla si richiude in una riga che dice per quanti sta — la coppia della sezione precedente si disegna come cinque righe: il documento, l’elenco che ci sta dentro, una riga richiusa che vale per un elemento, l’elemento aggiunto e una riga richiusa che vale per due.
  • Il conteggio accanto al titolo del risultato è ogni differenza che esiste. Quando l’elenco a schermo viene troncato per lunghezza quel conteggio non si muove, e un comando in fondo all’elenco mostra il resto.
~! $['port'] 8080 -> "8080"
- $['debug'] true
+ $['retries'] 3

Ecco che cosa produce il comando di copia per i due documenti della terza sezione: un segno per il genere, poi il percorso, poi i valori con una freccia in mezzo — un più per un membro aggiunto, un meno per uno rimosso, una tilde per un cambiamento e una tilde con un punto esclamativo dove è cambiato anche il tipo. È scritto in simboli e non in parole di proposito, così che un risultato incollato sia lo stesso testo in qualunque delle lingue del sito tu stessi leggendo, e non accorcia nulla: un valore che lo schermo ha troncato lì dentro è intero.

Quanto costa l’analisi, e che cosa lo strumento dice ad alta voce

Confrontare ciò in cui due documenti vengono analizzati significa convivere con quanto costa l’analisi, e analizzare JSON non è senza perdite. Qui pesa più che in qualunque altro punto di questo sito, perché la risposta che uno strumento di confronto meno può permettersi è «nessuna differenza» fra due documenti che invece differiscono davvero. Perciò il testo di ogni riquadro viene letto una seconda volta come testo, e ciò che l’analisi ha lasciato cadere viene riportato accanto al confronto invece di essere inghiottito.

  • Un nome di membro scritto due volte nello stesso oggetto. Solo la copia successiva sopravvive all’analisi, quindi solo la copia successiva è stata confrontata e la precedente non l’ha vista nessuno.
  • Un numero troppo lungo per sopravvivere come double. Due letterali diversi possono essere analizzati fino allo stesso valore, e gli identificatori sono esattamente il punto in cui accade: 12345678901234567890 e 12345678901234567891 sono due interi diversi e un solo numero analizzato, così che il confronto da solo direbbe identici due documenti i cui identificatori differiscono.
{"user": {"id": 1}, "user": {"id": 2}}

Quel documento viene analizzato fino a {"user": {"id": 2}}, e lo strumento lo dice sotto il riquadro in cui è stato incollato, con la riga e la colonna del primo caso e quanti ce ne sono in tutto. Nessuna delle due perdite ferma il confronto; entrambe gli stanno accanto. Un documento che non si analizza affatto viene riportato nello stesso posto, con la posizione del problema ovunque ce ne sia una da dare, e i due riquadri sono letti in modo indipendente — così due documenti rotti producono due errori insieme anziché uno dopo l’altro. Un documento annidato molto più a fondo di quanto un valore possa essere disegnato viene rifiutato senz’altro, e quello è un rifiuto e non una scheda bloccata.

Perché nulla di ciò che incolli esce da questa scheda

Entrambi i documenti restano nel tuo browser. Non c’è caricamento, non c’è richiesta e non c’è server che possa tenerne una copia: il confronto è aritmetica che gira dentro la pagina che hai già caricato, ed è anche il motivo per cui continua a funzionare con la rete spenta. È questo a rendere sicuro incollarci una risposta di produzione, la scheda di un cliente o un payload firmato.

Nemmeno viene conservato nulla. Ricarica la pagina ed entrambi i riquadri sono vuoti; non c’è cronologia, non c’è account e non c’è niente da cancellare dopo. L’unica cosa che esce mai è quello che copi tu.

Domande frequenti

Questo ignora l’ordine delle chiavi?
Sì, sempre, ed è la ragione per cui lo strumento esiste. Due oggetti con gli stessi membri e gli stessi valori sono identici comunque siano stati scritti, quindi un serializzatore che emette le chiavi in un ordine diverso a ogni esecuzione non compare mai come un cambiamento. L’indentazione, la minificazione, come è scritto un numero e come si esegue l’escape di un carattere sono ignorati per la stessa ragione, e niente di tutto ciò è un’impostazione che avresti potuto mettere al contrario.
Un membro impostato a null è la stessa cosa di un membro che non c’è?
No, e non esiste un’opzione per trattarli allo stesso modo. {"note": null} contro {} viene riportato come $['note'] rimosso. Un campo presente e vuoto è un’affermazione diversa da un campo che nessuno ha inviato — ogni linguaggio di schema e ogni linguaggio a tipi statici distingue i due — quindi confonderli qui butterebbe via qualcosa che i documenti stanno davvero dicendo.
[1, 2] e [2, 1] vengono riportati come uguali?
No. Un array JSON è ordinato, quindi due elenchi con gli stessi elementi in ordine diverso sono due documenti diversi, e chiamarli uguali sarebbe questo strumento che dice qualcosa di falso sul formato. Quello che ottieni è una rimozione e un’aggiunta: l’1 è stato rimosso in testa e aggiunto di nuovo dopo il 2. Gli oggetti stanno all’opposto, perché i loro membri sono nominati e non posizionati.
Che cosa significa il percorso su ogni riga?
È dove si trova la differenza dentro il documento, scritta come percorso normalizzato della RFC 9535: $ è il documento stesso, $['port'] un suo membro, $['hosts'][1] l’elemento all’indice 1 dell’elenco sotto hosts. È anche una query che puoi eseguire: incollala nel Tester JSONPath di questo sito insieme al documento a cui appartiene e seleziona esattamente quel nodo, e nessun altro.
Perché una riga ha mostrato due percorsi diversi?
Perché accoppiare due elenchi può lasciare lo stesso elemento a un indice diverso da ciascun lato. Confronta ["alpha", "beta"] con ["intro", "alpha", "beta!"] e ottieni due righe: intro è stato aggiunto in $[0], e beta è diventato beta! — che è $[1] nel primo documento e $[2] nel secondo. Dove i due percorsi coincidono, e di solito coincidono, ne viene mostrato uno solo.
Che cosa vuol dire un cambio di tipo su una riga?
Che il valore non è soltanto diverso ma una cosa di altra natura: un numero diventato una stringa, un valore diventato null, un oggetto diventato un elenco. Una porta scritta 8080 da un lato e "8080" dall’altro è il caso più comune, e di solito vuol dire che è cambiato un serializzatore o un client anziché i dati. Viene segnalato sulla riga perché un valore modificato e un contratto che deriva chiedono da te risposte diverse.
I miei due documenti sembrano identici ma uno scrive due volte la stessa chiave. Che cosa succede?
L’analisi tiene la copia successiva e lascia cadere la precedente, quindi il confronto vede un solo membro e può benissimo non riportare nessuna differenza. Lo strumento legge il testo grezzo separatamente proprio per questo e dice, sotto quel riquadro, che un nome è scritto due volte, quante volte in tutto e dov’è il primo. La stessa seconda lettura intercetta un intero troppo lungo per sopravvivere come double, che è l’altro modo in cui due documenti che differiscono possono essere analizzati in due che non differiscono.
Posso avere una patch da applicare?
No, ed è una decisione anziché qualcosa che manca. Una JSON Patch si applica un’operazione dopo l’altra, quindi gli indici al suo interno si spostano mentre gira; emetterne una è una promessa su un programma che eseguiresti contro i tuoi stessi dati, il che è un’affermazione molto più grossa che descrivere ciò che differisce. Quello che puoi portarti via, invece, è il rapporto come testo, dal comando di copia — lo stesso in ogni lingua del sito, e non accorciato da nessuna parte.
Qualcosa di ciò che incollo viene inviato a un server?
No. Entrambi i documenti sono confrontati nel tuo browser e nessuno dei due lo lascia: nessun caricamento, nessuna richiesta e nulla conservato fra una visita e l’altra. È questo a rendere sicuro incollarci una risposta di produzione, ed è il motivo per cui lo strumento funziona ancora con la rete spenta. Ricarica la pagina ed entrambi i riquadri sono di nuovo vuoti.

Strumenti correlati

  • Diff di testo

    Quando la modifica che vuoi vedere sta nella scrittura e non nei dati — una passata di indentazione, una chiave che si è spostata, o due file che non sono affatto JSON — il confronto che la mostra è quello riga per riga. È esattamente ciò che fa quella pagina, con opzioni per spazi e maiuscole e una patch copiabile.

  • Formattatore JSON

    Convalida e abbellisce il JSON, con posizioni di errore chiare.

  • Formattatore SQL

    Formatta e abbellisce SQL — più dialetti.

  • Formattatore XML

    Formatta XML e controlla che sia ben formato — abbellisci o minimizza.