JSON Toolkit

JSON in CSV: da dove viene l'intestazione e perché i valori cambiano

In sintesi: Nove esperimenti su un convertitore reale: la strategia "chiavi della prima riga" perde campi in silenzio, null, assenza e stringa vuota diventano tutti celle vuote, un intero a 20 cifre perde precisione già in JSON.parse, e un punto in una chiave fa collidere due percorsi diversi nella stessa colonna. Come si costruisce l'intestazione, quali valori si deformano e come scegliere una strategia in base all'uso a valle.

Intermedio~6 min di letturaPubblicato il 2026-11-04Aggiornato il 2026-11-04
Strategie di intestazione e fedeltà dei valori nella conversione da JSON a CSV

Quando una conversione da JSON a CSV perde dati, quasi mai accade durante la fase di "conversione". La perdita avviene in due domande che nessuno si è posto: chi ha deciso l'intestazione, e ogni cella resta riconoscibile come il valore che era?

Un campo scompare in trenta secondi

Molti convertitori — compresa l'idiomatica pipeline jq — prendono l'intestazione solo dal primo record. Riproduci le conseguenze in Python:

data = [{"id": 1, "name": "A"}, {"id": 2, "email": "b@x.com"}]
first_keys = list(data[0].keys())          # ['id', 'name']
rows = [[r.get(k, "") for k in first_keys] for r in data]
# L'email del secondo record scompare senza errori né avvisi

La chiave email non è nell'intestazione: non ha nemmeno il diritto di diventare una cella vuota — la colonna non è mai stata creata, e il valore viene gettato via in silenzio. La correzione è l'unione delle chiavi: attraversare tutti i record e creare una colonna per ogni chiave che compare in qualche punto. Gli stessi dati passati per l'unione producono l'intestazione id,name,email, con una cella vuota dove manca name — e nulla si perde.

Il convertitore diretto della nostra pagina /convert/csv applica esattamente questa strategia. Con l'input [{"id":1,"name":"A"},{"id":2,"email":"b@x.com"}], produce:

id,name,email
1,A,
2,,b@x.com

Le chiavi sono ordinate per prima comparsa e i campi assenti diventano celle vuote. jsonoperations e jsonbeam chiamano entrambe questa "la correzione del bug classico" — ma "usa l'unione" è solo metà della storia. L'unione ha il suo prezzo; è la sezione successiva.

Tre modi di costruire un'intestazione — e a chi conviene ciascuno

Strategia di intestazione Come si costruisce Forza Costo
Chiavi della prima riga Si legge solo il primo record Poche colonne, stabile, una riga di jq I nuovi campi dei record successivi vanno persi in silenzio
Unione delle chiavi Si leggono tutti i record Nessun campo perso Colonne scarse e molti vuoti quando le forme variano
Lista esplicita di colonne Definita a mano o da schema Completamente controllabile, riproducibile Va mantenuta; i campi nuovi richiedono aggiornamento manuale

Il criterio di scelta non è "quale è più sofisticato" ma cosa il destinatario fa di questo CSV:

  • Un collega che ci sbircia dentro: le chiavi della prima riga bastano, le forme dei record sono di solito uniformi.
  • Un report o una pipeline di dati: l'unione delle chiavi è il minimo sindacale — una cella vuota è essa stessa informazione.
  • Un export automatico ricorrente: la lista esplicita di colonne. La deriva di forma (campo aggiunto a monte, chiave rinominata) deve fallire rumorosamente, non produrre in silenzio una tabella che nessuno nota essere sbagliata.

La terza opzione è la più trascurata. Nell'automazione, "la conversione è riuscita" e "la conversione è corretta" sono due affermazioni diverse — e la lista esplicita di colonne è l'unico meccanismo che le separa.

Quando una chiave contiene un punto, i percorsi di appiattimento collidono

L'appiattimento in notazione puntata trasforma {"a": {"b": 2}} nella colonna a.b. Ma se la chiave stessa contiene un punto? Testato con pandas:

import pandas as pd
d = [{"a.b": 1, "a": {"b": 2}}]
pd.json_normalize(d, sep=".")
# Sopravvive una sola colonna: ['a.b'] — due chiavi completamente diverse collidono sullo stesso percorso

La chiave letterale a.b e il percorso annidato a → b producono lo stesso nome di colonna, uno sovrascrive l'altro, e i dati si corrompono in silenzio. È un esempio costruito, ma capita con dati reali: i nomi delle metriche di monitoraggio (cpu.usage) e i campi dei log (error.type) adorano i punti.

Due rimedi:

  1. Cambiare il separatore. sep="_" o un separatore personalizzato, dopo aver verificato che le chiavi originali non lo contengano mai.
  2. Cambiare la notazione dei percorsi. JSON Pointer usa / come separatore (/a/b); il convertitore Excel di toolvore segue questa strada. Ha la stessa classe di ambiguità — una chiave con / va scritta come ~1 — ma le regole di escape sono normalizzate nella RFC 6901 invece che inventate da ogni strumento.

Qualunque scelta faccia, scriva la regola nella documentazione dell'export. L'intestazione CSV è l'unico "schema" che questi dati portano; il lettore non trova le tue regole di appiattimento da nessun'altra parte.

Gli array: tre strategie, ognuna perde qualcosa di diverso

Gli array sono il vero ostacolo del JSON verso CSV: una cella non può contenere più valori. Tre strategie, con perdite verificate in pratica:

Strategia Forma di uscita Cosa si perde Ideale per
JSON-nella-cella L'array serializzato come stringa JSON nella cella Gli strumenti tabellari non possono filtrare né aggregare gli elementi Fedeltà prima di tutto, ispezione manuale
Colonne indicizzate tags.0, tags.1, una colonna ciascuna Il numero di colonne esplode con l'array più lungo; molti vuoti Piccoli array di lunghezza fissa
Righe esplose Una riga per elemento, campi padre duplicati Inflazione di righe; il comportamento degli array vuoti va definito a parte Analisi di dettaglio, aggregazione per elemento

I nostri convertitori /convert/csv e /convert/table usano JSON-nella-cella. L'input [{"orderId":"A001","items":[{"sku":"X1","qty":2},{"sku":"Y9","qty":1}]}] produce:

orderId,items
A001,"[{""sku"":""X1"",""qty"":2},{""sku"":""Y9"",""qty"":1}]"

Quando servono righe esplose, il record_path di pandas lo fa direttamente:

orders = [{"orderId": "A001", "total": 99,
           "items": [{"sku": "X1", "qty": 2}, {"sku": "Y9", "qty": 1}]}]
items = pd.json_normalize(orders, record_path=["items"],
                          meta=["orderId", "total"], sep=".")
# Due righe di dettaglio; orderId e total duplicati su ciascuna

Nell'esplosione ci sono tre trappole da decidere in anticipo: esplodere più array insieme costruisce un prodotto cartesiano; gli array vuoti non producono alcuna riga per default (la tua lista clienti perde persone); e i campi padre duplicati richiedono deduplicazione quando aggregi per orderId a valle. La guida di convertcsvonline elenca "decidere prima la granularità" come primo passo esattamente per questo — fissata la granularità, le tre domande hanno ciascuna una sola risposta.

Testato: valori che escono cambiati dalla conversione

Un test di fedeltà eseguito sul nostro convertitore reale (Node v22.13.1 + papaparse, la stessa implementazione di /convert/csv):

1. null, assenza e stringa vuota diventano tutti celle vuote. Input [{"a":null},{"b":""},{"c":1}] — tutte e tre le celle escono vuote. Il CSV non ha tre stati, solo "ci sono caratteri" e "non ci sono caratteri". Dopo un round-trip, tutti e tre si leggono come ""; la semantica originale è irrecuperabile.

2. Oggetti e array annidati diventano stringhe JSON. Le virgolette vengono raddoppiate secondo la RFC 4180 ({""name"":""张三""}), e incollando la cella in un qualsiasi parser JSON il valore torna — ecco perché JSON-nella-cella preserva la fedeltà.

3. Un intero a 20 cifre perde precisione già al parse — e la colpa non è del CSV. Input "id": 9007199254740993; nel CSV c'è già 9007199254740992. JSON.parse in JavaScript usa double IEEE 754: la perdita avviene durante il parsing, prima di qualsiasi convertitore CSV. Il json.load di Python la preserva integralmente. I campi tipo identificatore (ID snowflake, numeri d'ordine) dovrebbero arrivare come stringhe fin dal produttore. Approfondisci la precisione numerica in perché i numeri grandi perdono precisione.

4. Gli zeri iniziali nelle stringhe sono al sicuro al livello testo del CSV. "phone": "00123" resta 00123 nel CSV, perché è una stringa dall'inizio alla fine e nulla la scalfisce. A scalfire gli zeri è il passaggio successivo, in cui Excel tratta la cella come numero — vedi quattro modi per convertire JSON in Excel.

5. Campi con virgole, virgolette o a-capo. papaparse segue la RFC 4180: citare il campo, raddoppiare le virgolette interne. "He said \"hi\", ok" esce come "He said ""hi"", ok" e sopravvive intatto al round-trip. I serializzatori scritti a mano che aggirano una libreria falliscono quasi sempre proprio lì.

Round-trip: cosa cambia l'interruttore dell'inferenza

CSV → JSON deve indovinare un tipo per ogni cella. Il nostro convertitore disattiva l'inferenza di tipo per impostazione predefinita e restituisce tutto come stringa:

[{ "id": "9007199254740992", "phone": "00123" }]

Con typeInference attivo, id diventa un numero (la precisione è già persa), mentre phone resta stringa — le regole di inferenza escludono le forme con zero iniziale come 010, che non sono letterali numerici legali, così la stringa sopravvive per fortuna.

Memorizza questa asimmetria: l'inferenza spenta nel peggiore dei casi ti costa i tipi; l'inferenza accesa può costarti dati. Le colonne che hanno bisogno di numeri vanno convertite con definizioni esplicite di colonna, non affidate a un'inferenza globale. L'implementazione rowsToRecords del nostro articolo precedente JSON, CSV e dati tabellari è esattamente questo principio in codice.

Scegliere la strategia in base al destinatario: una tabella decisionale

Chi riceve il CSV Strategia di intestazione Gestione degli array Tipi
Un collega che lo apre una volta Unione delle chiavi JSON-nella-cella Senza inferenza
Excel / reportistica Unione delle chiavi JSON-nella-cella, o colonne indicizzate se lunghezza fissa Colonne identificatore forzate come testo
Import in database Lista esplicita di colonne Tabelle master/dettaglio separate, o esplosione Definizioni esplicite di colonna
BI / aggregazione Lista esplicita di colonne Righe esplose, granularità = più piccola unità di analisi Definizioni esplicite di colonna
Pipeline automatizzata Lista esplicita di colonne JSON-nella-cella, esplosione a valle Inferenza disattivata, conversione colonna per colonna

La legge comune: più sei vicino all'umano, più puoi permetterti di rilassare; più sei vicino alla macchina, più tutto deve essere esplicito.

Una checklist da copiare

  • [ ] L'intestazione viene da un'unione completa delle chiavi o da una lista esplicita? In caso negativo, puoi dimostrare che nessun record ha perso un campo?
  • [ ] Le chiavi possono contenere punti o barre? La collisione con il separatore di appiattimento è stata verificata?
  • [ ] La strategia degli array è documentata? Il comportamento degli array vuoti è deciso?
  • [ ] Per null, assenza e stringa vuota: cosa legge il destinatario per ciascuno? Scegli una regola e congélala.
  • [ ] Le colonne identificatore (ID, numeri d'ordine, telefoni) viaggiano come stringhe da capo a fine?
  • [ ] Dopo l'esplosione, il numero di righe è uguale alla somma delle lunghezze degli array? Verificato a campione?
  • [ ] I campi di esempio con virgole, virgolette e a-capo sopravvivono intatti a un round-trip?
  • [ ] L'inferenza globale di tipo è disattivata al reimport, con definizioni esplicite per le colonne numeriche?

Solo quando tutte e otto hanno una risposta netta, questo CSV è "corretto" — e non semplicemente "convertito con successo".

FAQ

Perché il convertitore non appiattisce gli oggetti annidati in colonne per impostazione predefinita?

L'appiattimento fa uscire di controllo il numero di colonne: l'annidamento profondo produce nomi come user.address.geo.lat, e combinato con l'unione delle chiavi, un solo record di forma diversa aggiunge una pila di colonne. Le stringhe JSON nelle celle preservano la fedeltà e un numero stabile di colonne; appiattisci esplicitamente quando ti serve.

Cosa fare se l'intestazione contiene nomi di colonna duplicati?

La collisione di due chiavi diverse dopo l'appiattimento (come a.b contro l'annidato a→b) va risolta a monte: cambia separatore o passa a JSON Pointer. Per i duplicati dentro lo stesso oggetto, i convertitori in genere tengono l'ultimo valore; i duplicati nell'intestazione dipendono dall'implementazione — l'unparse di papaparse non deduplica, ed Excel fonde o scarta i duplicati in silenzio all'apertura. Ecco perché è nella checklist.

I campi padre duplicati dopo l'esplosione sono un bug?

No — è design. Una riga esplosa è una riga di dettaglio, e una riga di dettaglio deve poter essere ricollegata al record padre tramite chiave. Aggrega a valle per la chiave padre; se la dimensione preoccupa, esporta il file di dettaglio e una tabella master separata.

Come verificare che la mia conversione non abbia perso alcun campo?

Fai un audit delle chiavi: calcola l'unione di tutte le chiavi del JSON sorgente, poi l'insieme delle intestazioni di colonna del CSV, e fanne la differenza. Una differenza non vuota significa campi persi. Nelle pipeline a lotti, codificala come asserzione nei tuoi test: qualsiasi cambio di convertitore che cominci a perdere campi fallirà automaticamente.