Che cosa si perde convertendo fra JSON, YAML, CSV e XML

In che cosa JSON, YAML, CSV, XML e TOML si differenziano su commenti, tipi, annidamento, date e ordine delle chiavi, e quale di questi dettagli ogni conversione fra loro butta via in silenzio.

Ogni conversione fra formati di dati lascia cadere ciò che la sorgente sapeva esprimere e la destinazione no. Alcune perdite sono evidenti: i commenti svaniscono nel momento in cui uno YAML diventa JSON. La maggior parte è silenziosa. Una data arriva come stringa, un array diventa cinque colonne, un ID che finiva per 3 adesso finisce per 2. L'abitudine utile è sapere quale dettaglio se ne andrà prima che se ne vada.

Che cosa può trasportare ciascun formato

CommentiTipiAnnidamentoDateOrdine delle chiaviChiavi duplicate
JSONNostring, number, boolean, null, object, arrayNessun tipo nativoNon ordinate secondo la specifica, conservate dalla maggior parte dei parserNon definito, di solito vince l'ultima
YAMLSì, #Quelli di JSON, più i timestamp e altro nella 1.1Sì nella 1.1, dipende dal tag nella 1.2Conservato nel file, non dopo il parsingNon valide, ma spesso accettate
CSVNoNessuno, ogni cella è testoNo, solo righe piatteNoFissato dalla riga di intestazioneLecite e ambigue
XMLSì, <!-- -->Nessuno senza uno schema, tipizzato con XSDSolo tramite uno schemaElementi ordinati, attributi noElementi ripetuti normali, attributi duplicati un errore
TOMLSì, #string, integer, float, boolean, date, time, date-time, array, tableSì, tramite le tabelleSì, di prima classeNon significativoUn errore, sempre

Due colonne causano la maggior parte delle sorprese. I tipi decidono se 007 sopravvive o arriva come il numero 7, e l'annidamento decide se una conversione in CSV è possibile senza inventarsi una convenzione.

Il CSV è piatto, e ogni cella è testo

Il CSV ha una forma sola: righe di campi. Non c'è modo di esprimere un oggetto dentro una cella, quindi un convertitore deve inventarsi qualcosa. Di solito appiattisce con percorsi separati da punti, così questo:

[{ "id": 1, "name": { "first": "Ada" }, "tags": ["admin", "ops"] }]

diventa:

id,name.first,tags.0,tags.1
1,Ada,admin,ops

Il processo inverso funziona in modo pulito finché non succedono due cose. Array di lunghezze diverse allargano l'intestazione fino al record più lungo, quindi una riga con tre tag aggiunge una colonna tags.2 e lascia a tutte le altre righe una cella vuota. E una chiave che contiene già un punto diventa poi indistinguibile da un percorso annidato, quindi il viaggio di ritorno ricostruisce la struttura sbagliata. L'alternativa è una stringa JSON dentro la cella, "[""admin"",""ops""]" con le virgolette raddoppiate come richiede il CSV, che non perde nulla e che nulla a valle capisce.

I tipi sono l'altra metà. Una cella contiene caratteri, quindi chi legge deve indovinare. Indovinare dà 007 come 7, 1.0 e 1 come un unico valore, un codice postale senza il suo zero iniziale, un codice aziendale TRUE come un booleano. Non indovinare dà ogni numero come stringa. L'inferenza dei tipi in Da CSV a JSON, o in Da TSV a JSON, è un'ipotesi con impostazioni predefinite ragionevoli, non un recupero, perché i tipi originali non sono mai stati scritti da nessuna parte.

Una cella vuota può significare stringa vuota, null, zero oppure non applicabile; decidi che cosa significa e dichiaralo, perché il file non può farlo. Scambiare i separatori, come fa Da CSV a TSV, è l'unica conversione sicura qui: non ci sono tipi da danneggiare.

YAML indovina il tipo al posto tuo

YAML deduce i tipi dal testo non quotato, ed è questo che lo rende piacevole da scrivere e rischioso da convertire.

country: no
version: 1.20
build: 010
start: 12:30

Con un parser YAML 1.1, che è quello che PyYAML e molte librerie più vecchie implementano ancora, tutto ciò diventa:

{ "country": false, "version": 1.2, "build": 8, "start": 750 }

Tutti e quattro hanno cambiato significato. no è il problema della Norvegia: YAML 1.1 tratta y, yes, no, on e off come booleani, quindi il codice di paese diventa false. 1.20 è un numero in virgola mobile, quindi lo zero finale è sparito. 010 corrisponde allo schema ottale e diventa 8. 12:30 corrisponde allo schema sessagesimale e diventa 750, un conteggio di minuti.

Un parser YAML 1.2 con schema core, come l'attuale js-yaml, legge lo stesso file con "no" come stringa, 010 come 10 e "12:30" come stringa. Un solo file, due parser, dati diversi. Le specifiche sono davvero in disaccordo; la 1.2 ha eliminato di proposito le regole sessagesimali e quelle sulle parole booleane.

La difesa è un solo carattere. Metti fra virgolette qualsiasi cosa debba essere testo: country: "no", version: "1.20". Il convertitore YAML ⇄ JSON mostra che cosa ha capito un parser di un file, perché in JSON l'ambiguità non ha dove nascondersi.

JSON non ha date, non ha commenti e ha un tetto di 53 bit

I numeri JSON sono double IEEE 754, quindi gli interi restano esatti solo fino a 2^53, cioè 9007199254740992. Oltre quel valore:

{ "id": 9007199254740993 }

Interpretalo e riserializzalo nella maggior parte dei linguaggi e torna come 9007199254740992. Il valore non è proprio rappresentabile, quindi più che arrotondato è indisponibile. È il motivo per cui Discord, Twitter e le altre API basate su snowflake mandano gli ID come stringhe: un intero a 64 bit non entra in un numero JSON.

Non esiste nemmeno un tipo data, quindi le date viaggiano come stringhe, per convenzione in ISO 8601, e niente segnala che "2026-08-25" è una data e non del testo. Passando per TOML o per un parser YAML 1.1 può diventare una data vera, e poi tornare indietro formattata in modo diverso. JSON non ha neanche i commenti né le virgole finali, quindi le annotazioni presenti in una sorgente YAML o TOML sono perse, non spostate. Generare interfacce TypeScript da un campione eredita entrambi i punti ciechi: una stringa non è una data, e un campo assente non è un campo facoltativo.

XML non entra dentro JSON, in nessuna delle due direzioni

XML distingue gli attributi dagli elementi figli, e JSON ha soltanto le chiavi.

<user id="7" active="true">
  <name>Ada</name>
  <tag>admin</tag>
  <tag>ops</tag>
</user>

Un convertitore sceglie una convenzione, di solito mette agli attributi il prefisso @ e riduce gli elementi ripetuti a un array. La perdita è di un tipo preciso: con un solo <tag> non c'è alcun array, quindi la forma del risultato dipende dai dati e non dallo schema, e il codice che si aspetta una lista si rompe sul record che ha un solo tag. Il contenuto misto come <p>Hello <b>there</b> friend</p> non ha un equivalente JSON, e spazi dei nomi, CDATA e commenti spariscono.

Il verso opposto perde altre cose. I nomi degli elementi non possono iniziare con una cifra né contenere spazi, quindi chiavi JSON arbitrarie vanno storpiate. Per convenzione null diventa un elemento vuoto oppure xsi:nil. Un array di primo livello ha bisogno di un elemento contenitore che nella sorgente non c'era. E niente in JSON registra se un valore un tempo era un attributo, quindi Da JSON a XML emette solo elementi. Da CSV a XML fa quella scelta apertamente, chiedendoti se vuoi elementi o attributi.

Ordine delle chiavi e chiavi duplicate

L'ordine delle chiavi non è garantito da nessuna parte e viene conservato quasi ovunque. La specifica JSON definisce gli oggetti come non ordinati, eppure tutti i parser diffusi mantengono l'ordine di inserimento. Ordinare le chiavi resta comunque più sicuro per qualsiasi cosa venga confrontata o sottoposta a checksum, dato che il confronto fra due documenti in cui le chiavi si sono solo spostate è illeggibile. Confrontare strutturalmente, come fa lo strumento Confronta JSON, mette in corrispondenza per chiave invece che per posizione ed evita del tutto la questione.

Le chiavi duplicate sono il bordo più tagliente. JSON non dice nulla, quindi i parser si comportano in modo diverso e la maggior parte tiene in silenzio l'ultimo valore. YAML le vieta e molti parser le accettano lo stesso. TOML le rifiuta senza appello. Il CSV consente due colonne con la stessa intestazione e lascia decidere a chi legge, motivo più che sufficiente per passare prima un validatore CSV su un file che non conosci. XML è l'unico in cui la ripetizione è significativa e non accidentale.

Convertire con intenzione

Ogni pipeline ha un passaggio con perdita. Scegli quale sia, invece di scoprirlo in seguito.

  • Arriva per ultimo al formato più stretto. Da XML a JSON a CSV si perdono gli attributi al primo passaggio e l'annidamento al secondo. Se la destinazione è il CSV, decidi quali campi contano e appiattisci deliberatamente.
  • Metti le virgolette prima di convertire, non dopo. Numeri di versione, codici di paese, codici postali e qualsiasi cosa con uno zero iniziale vanno fra virgolette già nella sorgente. Una volta che no è diventato false, il testo originale è irrecuperabile.
  • Controlla il risultato come dato. Formattalo, verifica il numero di record e ispeziona una riga notoriamente scomoda: la virgola dentro un nome, l'array più lungo, l'ID più grande.
  • Confronta un giro completo. Converti in uscita, riconverti indietro, confronta. Ciò che il confronto segnala è ciò che la conversione non riesce a trasportare.
  • Conserva l'originale. Il file convertito è un derivato, e quando fra tre mesi qualcuno chiederà se un campo era vuoto o assente, solo la sorgente potrà rispondere.