JSON in CSV
Oggetti e array annidati gestiti in modo esplicito, senza limiti di dimensione e senza caricamenti.
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
Il CSV è rettangolare e il JSON è un albero, quindi ogni convertitore deve prendere una serie di decisioni discrezionali. Quasi tutti le prendono in silenzio. Questa pagina le prende, le dichiara e ti lascia cambiarle.
Non c’è un limite di dimensione del file né una quota giornaliera, perché non c’è nessun server che possa imporli.
Le colonne sono l’unione, non il primo oggetto
La decisione con più conseguenze di tutte. Un convertitore che legge le chiavi dell’elemento zero elimina in silenzio ogni campo che compare solo nei record successivi, e te ne accorgi a valle, quando in un report manca una colonna.
Questo raccoglie ogni percorso su tutte le righe, nell’ordine in cui ciascuno è comparso. È più lento ed è corretto. Papa Parse, la libreria CSV JavaScript più diffusa, prende il primo oggetto per impostazione predefinita e richiede un’opzione columns esplicita per fare altrimenti: utile saperlo se te lo stai costruendo da solo.
Array dentro una riga: quattro politiche, un valore predefinito
Questo è il caso davvero ambiguo, e non esiste una risposta giusta, solo un valore predefinito giusto.
- Colonne indicizzate, il valore predefinito
- tags.0, tags.1 e così via. Senza perdita e con andata e ritorno. Un singolo array lungo fa esplodere il numero di colonne, ed è quello il costo.
- Unire in una cella
- Valori uniti da un punto e virgola. Leggibile, con perdita, e rischioso se un valore contiene il separatore.
- JSON in una cella
- L’array serializzato come testo JSON. Senza perdita e compatto, ma va rianalizzato a valle.
- Espandere in righe
- Una riga di output per ogni elemento dell’array, ripetendo le colonne scalari. È il record_path di json_normalize di pandas. Corretto per una relazione uno-a-molti e sbagliato per tutto il resto, quindi va attivato e si applica a un percorso specifico.
Due cose su Excel che non sono facoltative
- Il byte order mark UTF-8
- Excel non rileva l’UTF-8 in un CSV. Senza un BOM all’inizio del file legge i byte con la code page di sistema e ogni carattere accentato e ogni emoji arriva corrotto. Il BOM è attivo di default proprio per questo, e disattivarlo è a un clic per le pipeline che ci si strozzano.
- Injection di formule
- Una cella che comincia con =, +, - o @ viene eseguita come formula da Excel, Google Sheets e LibreOffice. Un valore =HYPERLINK("http://evil","clicca") in un CSV che hai generato tu diventa un link attivo nel foglio di calcolo di qualcun altro. OWASP la chiama CSV injection. Quelle celle ricevono per default un apostrofo davanti, e lo strumento ti dice quando lo ha fatto.
null contro stringa vuota
In JSON sono valori diversi ed Excel li mostra entrambi come vuoti, quindi quasi tutti i convertitori li appiattiscono insieme e la distinzione si perde. Qui null diventa una cella vuota senza virgolette e la stringa vuota una cella vuota tra virgolette, così l’andata e ritorno sopravvive. Costa due caratteri per stringa vuota e ne vale la pena.
How to do this in code
Convertire nel codice, con gli argomenti che decidono se è corretto.
py Python, pandas
encoding="utf-8-sig" è il modo di pandas per scrivere il BOM che serve a Excel. Un utf-8 semplice produce un file che Excel legge male.
import pandas as pd
# Flatten nested objects to dotted columns
df = pd.json_normalize(records)
df.to_csv('out.csv', index=False, encoding='utf-8-sig')
# One row per element of a nested array
df = pd.json_normalize(records, record_path='items', meta=['id']) sh jq
Usa @csv invece dell’interpolazione di stringhe. Applica le regole di quoting al posto tuo.
# Union of keys as the header, then the rows
jq -r '(map(keys) | add | unique) as $c
| $c, (.[] | [.[$c[]]])
| @csv' records.json > out.csv
# @csv quotes and escapes correctly; @text does not js JavaScript
import Papa from 'papaparse';
// Pass the union explicitly. Without it, Papa takes the keys of
// the first object and silently drops the rest.
const columns = [...new Set(records.flatMap(Object.keys))];
const csv = Papa.unparse(records, { columns }); go Go
w := csv.NewWriter(f)
w.Write(columns)
for _, rec := range records {
row := make([]string, len(columns))
for i, c := range columns {
row[i] = fmt.Sprint(rec[c])
}
w.Write(row)
}
w.Flush() Domande frequenti
- Perché il mio CSV ha caratteri strani in Excel?
- Il file è UTF-8 ed Excel l’ha letto con la code page di sistema. Lascia attiva l’opzione del byte order mark. Se il file va da qualche parte che non sia un foglio di calcolo, disattivala, perché alcuni parser trattano il BOM come parte del nome della prima colonna.
- Perché alcune celle iniziano con un apostrofo?
- Perché iniziavano con =, +, - o @, che i fogli di calcolo eseguono come formule. L’apostrofo neutralizza la cosa. Disattiva l’opzione se ti serve il valore grezzo e ti fidi di dove finirà il file.
- E se i miei dati non sono un array di oggetti?
- Un singolo oggetto diventa una riga. Un array di scalari diventa una colonna. Un contenitore come {"data": [...]} usa l’array interno e ti dice di aver fatto quella scelta, perché è un’ipotesi e non una regola.
- Quale delimitatore per l’Excel europeo?
- Il punto e virgola. Excel prende il delimitatore dal separatore di elenco del sistema, che è un punto e virgola nelle impostazioni locali in cui la virgola è il separatore decimale. Ecco perché un file separato da virgole si apre come una sola colonna su una macchina tedesca o francese.