JSON in CSV Python — Esempi DictWriter + pandas

·Backend Developer·Revisionato daPriya Sharma·Pubblicato

Usa il Da JSON a CSV gratuito direttamente nel tuo browser — nessuna installazione.

Prova Da JSON a CSV online →

Quasi ogni pipeline di dati arriva prima o poi allo stesso passaggio: un'API restituisce JSON, ma il consumatore successivo — un foglio di calcolo, uno script di importazione, un comando Redshift COPY — ha bisogno di CSV. Convertire JSON in CSV con Python sembra banale finché non ti imbatti in oggetti annidati, chiavi inconsistenti o valori datetime che richiedono una gestione speciale. Python offre due percorsi solidi: i moduli integrati json + csv per script senza dipendenze, e pandas per l'appiattimento di strutture annidate e dataset più grandi — oppure il convertitore online JSON in CSV per conversioni rapide una tantum senza scrivere codice. Questa guida copre entrambi gli approcci dall'inizio alla fine, con esempi eseguibili in Python 3.8+.

  • csv.DictWriter converte una lista di dict in CSV senza dipendenze — usa json.load() per analizzare, poi writeheader() + writerows().
  • Apri sempre i file CSV con newline="" su Windows per evitare righe vuote tra le righe di dati.
  • pd.json_normalize() appiattisce il JSON annidato in un DataFrame piatto prima di chiamare to_csv() — gestisce automaticamente la nidificazione a più livelli.
  • Passa index=False a DataFrame.to_csv() — senza di esso, pandas scrive una colonna con i numeri di riga non desiderata.
  • Per file superiori a 500 MB, usa ijson per il parsing JSON in streaming combinato con csv.DictWriter per un utilizzo costante della memoria.

Cos'è la conversione JSON in CSV?

La conversione da JSON a CSV trasforma un array di oggetti JSON in un formato tabellare dove ogni oggetto diventa una riga e ogni chiave diventa un'intestazione di colonna. JSON è gerarchico — gli oggetti possono annidarsi arbitrariamente in profondità. CSV è piatto — ogni valore si trova in una griglia riga-colonna. La conversione funziona correttamente quando ogni oggetto condivide lo stesso insieme di chiavi di primo livello. Gli oggetti annidati, gli array e le chiavi inconsistenti sono i casi in cui le cose si complicano. I dati grezzi restano identici; cambia solo la struttura.

Before · json
After · json
[{"order_id":"ord_91a3","total":149.99,"status":"shipped"},
 {"order_id":"ord_b7f2","total":34.50,"status":"pending"}]
order_id,total,status
ord_91a3,149.99,shipped
ord_b7f2,34.50,pending

csv.DictWriter — Converti JSON in CSV senza Pandas

Il modulo csv è incluso in ogni installazione Python. Nessun pip install, nessuna ginnastica con ambienti virtuali. csv.DictWriter prende una lista di dizionari e scrive ciascuno come una riga CSV, mappando le chiavi del dict alle intestazioni di colonna. Il parametro fieldnames controlla sia l'ordine delle colonne che quali chiavi vengono incluse.

Python 3.8+ — esempio minimale json in csv
import json
import csv

# Dati JSON di esempio — un array di oggetti ordine
json_string = """
[
  {"order_id": "ord_91a3", "product": "Wireless Keyboard", "quantity": 2, "unit_price": 74.99},
  {"order_id": "ord_b7f2", "product": "USB-C Hub", "quantity": 1, "unit_price": 34.50},
  {"order_id": "ord_c4e8", "product": "Monitor Stand", "quantity": 3, "unit_price": 29.95}
]
"""

records = json.loads(json_string)

with open("orders.csv", "w", newline="", encoding="utf-8") as csvfile:
    writer = csv.DictWriter(csvfile, fieldnames=records[0].keys())
    writer.writeheader()
    writer.writerows(records)

# orders.csv:
# order_id,product,quantity,unit_price
# ord_91a3,Wireless Keyboard,2,74.99
# ord_b7f2,USB-C Hub,1,34.50
# ord_c4e8,Monitor Stand,3,29.95

L'argomento newline="" su open() non è opzionale su Windows. Senza di esso, si ottengono doppi carriage return — che appaiono come righe vuote tra ogni riga di dati in Excel. Su macOS e Linux è innocuo, quindi è meglio includerlo sempre.

Il codice sopra usa json.loads() per una stringa. Usa json.load() (senza la s finale) quando si legge da un file handle. Questo confonde continuamente le persone — uno legge una stringa, l'altro legge un oggetto file.

Python 3.8+ — leggi file JSON, scrivi file CSV
import json
import csv

with open("server_metrics.json", encoding="utf-8") as jf:
    metrics = json.load(jf)  # json.load() per oggetti file

# I fieldnames espliciti controllano l'ordine delle colonne
columns = ["timestamp", "hostname", "cpu_percent", "memory_mb", "disk_io_ops"]

with open("server_metrics.csv", "w", newline="", encoding="utf-8") as cf:
    writer = csv.DictWriter(cf, fieldnames=columns, extrasaction="ignore")
    writer.writeheader()
    writer.writerows(metrics)

# Compaiono solo le cinque colonne specificate, esattamente in quell'ordine

Impostare extrasaction="ignore" scarta silenziosamente le chiavi nei dict che non sono nella lista fieldnames. Il default è "raise", che lancia un ValueError se un dict ha una chiave inattesa. Scegli quello che si adatta alla tua tolleranza per le sorprese.

Nota:csv.DictWriter vs csv.writer: DictWriter mappa automaticamente le chiavi del dict alle posizioni delle colonne. csv.writer scrive liste grezze come righe — sei tu a gestire l'ordinamento delle colonne. DictWriter è quasi sempre la scelta giusta per JSON-to-CSV perché i record JSON sono già dizionari.

Il modulo csv di Python include tre dialect predefiniti: excel (delimitatore virgola, terminazioni CRLF — il default), excel-tab (delimitatore tab, terminazioni CRLF) e unix (terminazioni LF, virgolette attorno a tutti i campi non numerici). Passa il nome del dialect come argomento dialect a csv.DictWriter. Puoi anche definire un dialect personalizzato con csv.register_dialect() quando il tuo sistema di destinazione ha regole di quotatura o delimitatori insoliti. Per la maggior parte dei workflow JSON-to-CSV il dialect excel è corretto, ma passa a unix quando scrivi file che verranno elaborati da strumenti POSIX come awk o sort.

Gestire i tipi non standard: datetime, UUID e Decimal

Il JSON proveniente dalle API spesso contiene date come stringhe ISO, UUID come stringhe con trattini e valori monetari come float. Quando li analizzi in oggetti Python per l'elaborazione prima di scrivere il CSV, devi riconvertirli in stringhe. Il modulo csv chiama str() su ogni valore, quindi la maggior parte dei tipi funziona automaticamente. Ma gli oggetti datetime producono rappresentazioni stringa predefinite disordinate, e i valori Decimal richiedono una formattazione esplicita per evitare la notazione scientifica.

Python 3.8+ — pre-elabora datetime e Decimal prima della scrittura CSV
import json
import csv
from datetime import datetime, timezone
from decimal import Decimal
from uuid import UUID

# Simulazione di risposta API analizzata con tipi Python
transactions = [
    {
        "txn_id": UUID("a1b2c3d4-e5f6-7890-abcd-ef1234567890"),
        "created_at": datetime(2026, 3, 15, 9, 30, 0, tzinfo=timezone.utc),
        "amount": Decimal("1249.99"),
        "currency": "USD",
        "merchant": "CloudHost Inc.",
    },
    {
        "txn_id": UUID("b2c3d4e5-f6a7-8901-bcde-f12345678901"),
        "created_at": datetime(2026, 3, 15, 14, 12, 0, tzinfo=timezone.utc),
        "amount": Decimal("87.50"),
        "currency": "EUR",
        "merchant": "DataSync GmbH",
    },
]

def prepare_row(record: dict) -> dict:
    """Converti i tipi non stringa in stringhe compatibili con CSV."""
    return {
        "txn_id": str(record["txn_id"]),
        "created_at": record["created_at"].isoformat(),
        "amount": f"{record['amount']:.2f}",
        "currency": record["currency"],
        "merchant": record["merchant"],
    }

with open("transactions.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["txn_id", "created_at", "amount", "currency", "merchant"])
    writer.writeheader()
    for txn in transactions:
        writer.writerow(prepare_row(txn))

# transactions.csv:
# txn_id,created_at,amount,currency,merchant
# a1b2c3d4-e5f6-7890-abcd-ef1234567890,2026-03-15T09:30:00+00:00,1249.99,USD,CloudHost Inc.
# b2c3d4e5-f6a7-8901-bcde-f12345678901,2026-03-15T14:12:00+00:00,87.50,EUR,DataSync GmbH

La funzione prepare_row() è l'approccio corretto qui. Invece di cercare di insegnare a csv.DictWriter i tipi personalizzati, normalizzi ogni record in stringhe prima della scrittura. Preferisco chiamare .isoformat() esplicitamente sugli oggetti datetime piuttosto che affidarsi a str() — il formato di output è più prevedibile e i parser downstream gestiscono ISO 8601 in modo affidabile.

Attenzione:Se lasci passare i valori Decimal senza formattarli, i numeri molto piccoli o molto grandi potrebbero essere renderizzati in notazione scientifica (es. 1.5E+7). Formatta sempre Decimal con una f-string esplicita come f"{value:.2f}" quando scrivi dati finanziari in CSV.

Un pattern alternativo per pipeline con molti tipi personalizzati è estendere json.JSONEncoder. Crea una sottoclasse, sovrascrivi il metodo default() per restituire un valore serializzabile in JSON per ogni tipo personalizzato, poi passa la sottoclasse come argomento cls a json.dumps(). La ri-codifica tramite l'encoder personalizzato prima della scrittura nel CSV normalizza tutti i tipi in un solo passaggio senza una chiamata a prepare_row() per ogni riga. Il pattern prepare_row() mostrato sopra è più semplice per script una tantum; l'approccio con la sottoclasse JSONEncoder scala meglio quando lo stesso modello di dominio con tipi personalizzati è condiviso tra molte fasi della pipeline o microservizi.

Riferimento parametri csv.DictWriter

La firma completa del costruttore è csv.DictWriter(f, fieldnames, restval="", extrasaction="raise", dialect="excel", **fmtparams). La maggior parte di questi ha valori predefiniti sensati. Quelli che cambierai davvero sono fieldnames, delimiter e extrasaction.

Parametro
Tipo
Default
Descrizione
f
file object
(obbligatorio)
Qualsiasi oggetto con un metodo write() — tipicamente restituito da open()
fieldnames
sequence
(obbligatorio)
Lista di chiavi che definisce l'ordine delle colonne nell'output CSV
restval
str
""
Valore scritto quando a un dict manca una chiave presente in fieldnames
extrasaction
str
"raise"
"raise" lancia ValueError per chiavi extra; "ignore" le scarta silenziosamente
dialect
str / Dialect
"excel"
Regole di formattazione predefinite — "excel", "excel-tab" o "unix"
delimiter
str
","
Singolo carattere che separa i campi — usa "\t" per output TSV
quotechar
str
"
Carattere usato per racchiudere tra virgolette i campi contenenti il delimitatore
quoting
int
csv.QUOTE_MINIMAL
Controlla quando vengono applicate le virgolette — MINIMAL, ALL, NONNUMERIC, NONE
lineterminator
str
"\r\n"
Stringa aggiunta dopo ogni riga — sovrascrivila con "\n" per output in stile Unix

pandas — Converti JSON in CSV con i DataFrame

Se lavori già in una codebase orientata a pandas, o il tuo JSON ha oggetti annidati che devi appiattire, l'approccio pandas richiede significativamente meno codice rispetto alla versione stdlib. Il compromesso: pandas è una dipendenza da ~30 MB. Per uno script usa e getta, va bene. Per un'immagine Docker da mandare in produzione, l'approccio stdlib mantiene le cose più leggere.

Python 3.8+ — pandas read_json poi to_csv
import pandas as pd

# Leggi l'array JSON direttamente in un DataFrame
df = pd.read_json("warehouse_inventory.json")

# Scrivi in CSV — index=False previene i numeri di riga auto-generati
df.to_csv("warehouse_inventory.csv", index=False)

# Tutto qui. Due righe. pandas inferisce i tipi di colonna automaticamente.

Il flag index=False è una di quelle cose che devi cercare ogni volta. Senza di esso, pandas scrive una colonna 0, 1, 2, ... come prima colonna del tuo CSV. Nessuno lo vuole.

Appiattire JSON annidato con json_normalize

Le risposte API reali sono raramente piatte. Gli ordini contengono indirizzi di spedizione, gli utenti contengono preferenze annidate, gli eventi di telemetria contengono metadati annidati. pd.json_normalize() percorre i dizionari annidati e li appiattisce in colonne con nomi separati da punti.

Python 3.8+ — appiattisci JSON annidato usando json_normalize
import json
import pandas as pd

api_response = """
[
  {
    "order_id": "ord_91a3",
    "placed_at": "2026-03-15T09:30:00Z",
    "customer": {
      "name": "Sarah Chen",
      "email": "s.chen@example.com",
      "tier": "premium"
    },
    "shipping": {
      "method": "express",
      "address": {
        "city": "Portland",
        "state": "OR",
        "zip": "97201"
      }
    },
    "total": 299.95
  },
  {
    "order_id": "ord_b7f2",
    "placed_at": "2026-03-15T14:12:00Z",
    "customer": {
      "name": "James Park",
      "email": "j.park@example.com",
      "tier": "standard"
    },
    "shipping": {
      "method": "standard",
      "address": {
        "city": "Austin",
        "state": "TX",
        "zip": "73301"
      }
    },
    "total": 87.50
  }
]
"""

orders = json.loads(api_response)

# json_normalize appiattisce i dict annidati — sep controlla il delimitatore
df = pd.json_normalize(orders, sep="_")
df.to_csv("flat_orders.csv", index=False)

# Colonne risultanti:
# order_id, placed_at, customer_name, customer_email, customer_tier,
# shipping_method, shipping_address_city, shipping_address_state,
# shipping_address_zip, total

Il parametro sep="_" controlla come vengono uniti i nomi delle chiavi annidate. Il default è ".", che produce colonne come customer.name. Preferisco i trattini bassi perché i punti nei nomi delle colonne causano problemi con gli import SQL e alcune formule dei fogli di calcolo.

Per le risposte API che avvolgono l'array di record sotto una chiave annidata, usa il parametro record_path. Se la risposta appare come {"data": {"orders": [...]}}, passa record_path=["data", "orders"] per navigare fino alla lista corretta. Il parametro opzionale meta ti permette di estrarre campi di livello superiore insieme ai record annidati — utile quando la risposta include informazioni di paginazione di primo livello (numero di pagina, conteggio totale) che vuoi come colonna in ogni riga. Insieme, record_path e meta gestiscono la maggior parte delle strutture di risposta API annidate del mondo reale senza preprocessing personalizzato.

Riferimento parametri DataFrame.to_csv()

DataFrame.to_csv() ha più di 20 parametri. Questi sono quelli che contano per i workflow JSON-to-CSV.

Parametro
Tipo
Default
Descrizione
path_or_buf
str / Path / None
None
Percorso del file o buffer — None restituisce il CSV come stringa
sep
str
","
Delimitatore di campo — usa "\t" per TSV
index
bool
True
Scrive l'indice di riga come prima colonna — quasi sempre impostato su False
columns
list
None
Sottoinsieme e riordinamento delle colonne nell'output
header
bool / list
True
Scrive i nomi delle colonne — impostare False quando si aggiunge a un file esistente
encoding
str
"utf-8"
Codifica dell'output — usa "utf-8-sig" per compatibilità con Excel su Windows
na_rep
str
""
Rappresentazione stringa per valori mancanti (NaN, None)
quoting
int
csv.QUOTE_MINIMAL
Controlla quando i campi vengono racchiusi tra virgolette
Python 3.8+ — to_csv con override comuni dei parametri
import pandas as pd

df = pd.read_json("telemetry_events.json")

# Output TSV con codifica esplicita e gestione dei valori mancanti
df.to_csv(
    "telemetry_events.tsv",
    sep="\t",
    index=False,
    encoding="utf-8",
    na_rep="NULL",
    columns=["event_id", "timestamp", "source", "severity", "message"],
)

# Scrivi su stdout per il piping negli script shell
print(df.to_csv(index=False))

# Restituisce come stringa (nessun file scritto)
csv_string = df.to_csv(index=False)
print(len(csv_string), "characters")

Converti JSON in CSV da un file e da una risposta API

I due scenari più comuni nel mondo reale: leggere JSON da un file su disco e convertirlo, oppure recuperare JSON da un'API HTTP e salvare il risultato come CSV. In fase di sviluppo puoi cavartela senza gestione degli errori. In produzione, quella scelta diventa un alert alle 2 di notte. I file potrebbero non esistere, le API potrebbero restituire codici di stato 4xx o 5xx invece di JSON, il corpo della risposta potrebbe essere un oggetto di errore invece di un array, o il JSON potrebbe essere troncato a causa di un timeout di rete. I pattern seguenti gestiscono tutti questi casi esplicitamente, registrano gli errori su stderr e restituiscono un conteggio delle righe così i chiamanti possono rilevare output con zero righe e inviare avvisi di conseguenza.

File su disco — Leggi, Converti, Salva

Python 3.8+ — converti file JSON in CSV con gestione degli errori
import json
import csv
import sys

def json_file_to_csv(input_path: str, output_path: str) -> int:
    """Converti un file JSON contenente un array di oggetti in CSV.
    Restituisce il numero di righe scritte.
    """
    try:
        with open(input_path, encoding="utf-8") as jf:
            data = json.load(jf)
    except FileNotFoundError:
        print(f"Error: {input_path} not found", file=sys.stderr)
        return 0
    except json.JSONDecodeError as exc:
        print(f"Error: invalid JSON in {input_path}: {exc.msg} at line {exc.lineno}", file=sys.stderr)
        return 0

    if not isinstance(data, list) or not data:
        print(f"Error: expected a non-empty JSON array in {input_path}", file=sys.stderr)
        return 0

    # Raccogli tutte le chiavi univoche tra tutti i record — gestisce schemi inconsistenti
    all_keys: list[str] = []
    seen: set[str] = set()
    for record in data:
        for key in record:
            if key not in seen:
                all_keys.append(key)
                seen.add(key)

    with open(output_path, "w", newline="", encoding="utf-8") as cf:
        writer = csv.DictWriter(cf, fieldnames=all_keys, restval="", extrasaction="ignore")
        writer.writeheader()
        writer.writerows(data)

    return len(data)

rows = json_file_to_csv("deploy_logs.json", "deploy_logs.csv")
print(f"Wrote {rows} rows to deploy_logs.csv")

Risposta API HTTP — Recupera e Converti

Python 3.8+ — recupera JSON dall'API e salva come CSV
import json
import csv
import urllib.request
import urllib.error

def api_response_to_csv(url: str, output_path: str) -> int:
    """Recupera JSON da un endpoint REST API e scrivilo come CSV."""
    try:
        req = urllib.request.Request(url, headers={"Accept": "application/json"})
        with urllib.request.urlopen(req, timeout=30) as resp:
            if resp.status != 200:
                print(f"Error: API returned status {resp.status}")
                return 0
            body = resp.read().decode("utf-8")
    except urllib.error.URLError as exc:
        print(f"Error: could not reach {url}: {exc.reason}")
        return 0

    try:
        records = json.loads(body)
    except json.JSONDecodeError as exc:
        print(f"Error: API returned invalid JSON: {exc.msg}")
        return 0

    if not isinstance(records, list) or not records:
        print("Error: expected a non-empty JSON array from the API")
        return 0

    with open(output_path, "w", newline="", encoding="utf-8") as cf:
        writer = csv.DictWriter(cf, fieldnames=records[0].keys())
        writer.writeheader()
        writer.writerows(records)

    return len(records)

rows = api_response_to_csv(
    "https://api.internal.example.com/v2/deployments?status=completed",
    "completed_deployments.csv",
)
print(f"Exported {rows} deployments to CSV")
Nota:L'esempio sopra usa urllib dalla libreria standard per mantenere lo script senza dipendenze. Se hai requests installato, sostituisci la sezione urllib con resp = requests.get(url, timeout=30); records = resp.json() — il resto del codice di scrittura CSV rimane identico.

Conversione JSON in CSV da riga di comando

A volte hai solo bisogno di un one-liner nel terminale. Il flag -c di Python ti permette di eseguire una conversione rapida senza creare un file script. Per trasformazioni più complesse, usa prima il pipe attraverso jq per ridisegnare i dati, poi converti.

bash — one-liner conversione json in csv
# One-liner Python: legge JSON da stdin, scrive CSV su stdout
cat orders.json | python3 -c "
import json, csv, sys
data = json.load(sys.stdin)
w = csv.DictWriter(sys.stdout, fieldnames=data[0].keys())
w.writeheader()
w.writerows(data)
"

# Salva l'output in un file
cat orders.json | python3 -c "
import json, csv, sys
data = json.load(sys.stdin)
w = csv.DictWriter(sys.stdout, fieldnames=data[0].keys())
w.writeheader()
w.writerows(data)
" > orders.csv
bash — script CLI autonomo con argparse
# Salva come json2csv.py ed esegui: python3 json2csv.py input.json -o output.csv
python3 -c "
import json, csv, argparse, sys

parser = argparse.ArgumentParser(description='Convert JSON array to CSV')
parser.add_argument('input', help='Path to JSON file')
parser.add_argument('-o', '--output', default=None, help='Output CSV path (default: stdout)')
parser.add_argument('-d', '--delimiter', default=',', help='CSV delimiter')
args = parser.parse_args()

with open(args.input) as f:
    data = json.load(f)

out = open(args.output, 'w', newline='') if args.output else sys.stdout
writer = csv.DictWriter(out, fieldnames=data[0].keys(), delimiter=args.delimiter)
writer.writeheader()
writer.writerows(data)
if args.output:
    out.close()
    print(f'Wrote {len(data)} rows to {args.output}', file=sys.stderr)
" "$@"
bash — usando jq + csvkit per trasformazioni complesse
# Installa csvkit: pip install csvkit

# jq appiattisce e seleziona i campi, in2csv gestisce la formattazione CSV
cat api_response.json | jq '[.[] | {id: .order_id, customer: .customer.name, total}]' | in2csv -f json > orders.csv

# Miller (mlr) è un'altra opzione per JSON-to-CSV
mlr --json2csv cat orders.json > orders.csv

Miller (mlr) è un binario autonomo che tratta JSON, CSV e TSV come formati di prima classe senza richiedere un runtime Python. Il flag --json2csv converte l'input JSON in CSV in un singolo passaggio, e puoi concatenare verbi Miller per filtrare, ordinare o rinominare le colonne nello stesso comando prima di scrivere l'output. Installalo via Homebrew su macOS (brew install miller) o il tuo gestore di pacchetti Linux. È particolarmente utile nelle pipeline CI dove vuoi una conversione JSON-to-CSV veloce senza avviare un ambiente Python.

Alternativa ad alte prestazioni — pandas con pyarrow

Per dataset nell'ordine delle decine di milioni di righe, pandas con il backend pyarrow legge e scrive significativamente più velocemente rispetto al default. Il motore Arrow in C elabora i dati colonnari in modo più efficiente del modulo csv riga per riga di Python. L'API rimane la stessa — imposti solo il parametro engine.

bash — installa pyarrow
pip install pyarrow
Python 3.8+ — pandas con pyarrow per una scrittura CSV più veloce
import pandas as pd

# Leggi JSON con il motore pyarrow (parsing più veloce per file grandi)
df = pd.read_json("sensor_readings.json", engine="pyarrow")

# to_csv non ha un parametro engine, ma le operazioni DataFrame
# tra lettura e scrittura beneficiano del layout colonnare di pyarrow
df.to_csv("sensor_readings.csv", index=False)

# Per esportazioni davvero grandi, considera di scrivere in Parquet invece che in CSV
# — formato binario, 5-10x più piccolo, preserva i tipi
df.to_parquet("sensor_readings.parquet", engine="pyarrow")

Se stai elaborando più di qualche centinaio di MB di JSON e il consumatore finale accetta Parquet, salta completamente il CSV. Parquet è più piccolo, preserva i tipi delle colonne, e sia Redshift che BigQuery lo caricano nativamente. CSV è un formato con perdita — ogni valore diventa una stringa.

Output del terminale con evidenziazione della sintassi

La libreria rich renderizza tabelle con bordi, allineamento e colori nel terminale — utile per visualizzare in anteprima una conversione durante lo sviluppo senza aprire il file di output.

bash — installa rich
pip install rich
Python 3.8+ — anteprima output CSV nel terminale con rich
import json
from rich.console import Console
from rich.table import Table

json_string = """
[
  {"hostname": "web-prod-1", "cpu_percent": 72.3, "memory_mb": 3840, "uptime_hours": 720},
  {"hostname": "web-prod-2", "cpu_percent": 45.1, "memory_mb": 2560, "uptime_hours": 168},
  {"hostname": "db-replica-1", "cpu_percent": 91.7, "memory_mb": 7680, "uptime_hours": 2160}
]
"""

records = json.loads(json_string)
console = Console()

table = Table(title="Server Metrics Preview", show_lines=True)
for key in records[0]:
    table.add_column(key, style="cyan" if key == "hostname" else "white")

for row in records:
    table.add_row(*[str(v) for v in row.values()])

console.print(table)
# Renderizza una tabella con colori ed evidenziazione nel terminale
Attenzione:Rich è solo per la visualizzazione nel terminale. Non usarlo per generare file CSV — aggiunge codici di escape ANSI che corrompono l'output. Scrivi su file con csv.DictWriter o DataFrame.to_csv(), e usa rich solo per l'anteprima.

Lavorare con file JSON di grandi dimensioni

json.load() legge l'intero file in memoria. Per un file JSON da 200 MB, questo significa ~200 MB di testo grezzo più l'overhead degli oggetti Python — facilmente 500 MB+ di utilizzo heap. Per file superiori a 100 MB, usa lo streaming dell'input con ijson e scrivi le righe CSV man mano che procedi.

bash — installa ijson
pip install ijson

Streaming di un array JSON in CSV con ijson

Python 3.8+ — stream di un array JSON grande in CSV con memoria costante
import ijson
import csv

def stream_json_to_csv(json_path: str, csv_path: str) -> int:
    """Converti un array JSON grande in CSV senza caricarlo tutto in memoria."""
    with open(json_path, "rb") as jf, open(csv_path, "w", newline="", encoding="utf-8") as cf:
        # ijson.items produce ogni elemento dell'array di primo livello uno alla volta
        records = ijson.items(jf, "item")

        first_record = next(records)
        fieldnames = list(first_record.keys())

        writer = csv.DictWriter(cf, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerow(first_record)

        count = 1
        for record in records:
            writer.writerow(record)
            count += 1

    return count

rows = stream_json_to_csv("clickstream_2026_03.json", "clickstream_2026_03.csv")
print(f"Streamed {rows} records to CSV")

NDJSON / JSON Lines — Un oggetto per riga

NDJSON (Newline-Delimited JSON), chiamato anche JSON Lines o .jsonl, memorizza un oggetto JSON valido per riga senza un array avvolgente. Questo formato è comune nelle pipeline di log, negli stream di eventi (Kafka, Kinesis) e nelle esportazioni bulk da servizi come Elasticsearch e BigQuery. Poiché ogni riga è un oggetto JSON autonomo, puoi elaborare un file NDJSON con un semplice ciclo for Python sul file handle — non c'è bisogno della libreria ijson. La memoria rimane costante indipendentemente dalla dimensione del file, rendendo questo l'approccio di streaming più semplice quando i tuoi dati sorgente sono già in formato JSON Lines.

Python 3.8+ — converti NDJSON in CSV riga per riga
import json
import csv

def ndjson_to_csv(ndjson_path: str, csv_path: str) -> int:
    """Converti un file JSON newline-delimited in CSV, una riga alla volta."""
    with open(ndjson_path, encoding="utf-8") as nf:
        first_line = nf.readline()
        first_record = json.loads(first_line)
        fieldnames = list(first_record.keys())

        with open(csv_path, "w", newline="", encoding="utf-8") as cf:
            writer = csv.DictWriter(cf, fieldnames=fieldnames)
            writer.writeheader()
            writer.writerow(first_record)

            count = 1
            for line in nf:
                line = line.strip()
                if not line:
                    continue
                try:
                    record = json.loads(line)
                    writer.writerow(record)
                    count += 1
                except json.JSONDecodeError:
                    continue  # salta le righe malformate

    return count

rows = ndjson_to_csv("access_log.ndjson", "access_log.csv")
print(f"Converted {rows} log entries to CSV")
Nota:Passa allo streaming quando il file JSON supera i 100 MB. Un array JSON da 1 GB caricato con json.load() può consumare 3–5 GB di RAM a causa dell'overhead degli oggetti Python. Con ijson, la memoria rimane costante indipendentemente dalla dimensione del file. Se hai solo bisogno di una conversione rapida di un file piccolo, incollalo nel convertitore JSON in CSV invece.

Errori comuni

newline='' mancante in open() — righe vuote su Windows

Problema: Il modulo csv scrive terminazioni di riga . Senza newline='', la modalità testo di Python aggiunge un altro su Windows, producendo output a doppia spaziatura.

Soluzione: Passa sempre newline='' quando apri un file per la scrittura CSV. È innocuo su macOS/Linux.

Before · Python
After · Python
with open("output.csv", "w") as f:
    writer = csv.DictWriter(f, fieldnames=columns)
    writer.writeheader()
    writer.writerows(data)
# Righe vuote tra ogni riga di dati su Windows
with open("output.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=columns)
    writer.writeheader()
    writer.writerows(data)
# Output pulito su tutte le piattaforme
Dimenticare index=False in pandas to_csv()

Problema: Senza index=False, pandas antepone una colonna con numeri di riga auto-incrementanti (0, 1, 2, ...) che inquina il CSV con dati che non erano mai nel JSON originale.

Soluzione: Passa index=False a to_csv(). Se hai effettivamente bisogno di una colonna indice, nominala esplicitamente con df.index.name = 'row_num'.

Before · Python
After · Python
df = pd.read_json("events.json")
df.to_csv("events.csv")
# Il CSV ottiene una colonna extra senza nome: ,event_id,timestamp,...
# La virgola iniziale rompe molti parser CSV
df = pd.read_json("events.json")
df.to_csv("events.csv", index=False)
# CSV pulito: event_id,timestamp,...
Usare records[0].keys() quando i record hanno chiavi inconsistenti

Problema: Se gli oggetti JSON hanno chiavi diverse (alcuni record hanno campi opzionali), usare le chiavi del primo record come fieldnames scarta silenziosamente le colonne che appaiono solo nei record successivi.

Soluzione: Raccogli tutte le chiavi univoche tra tutti i record prima di creare il DictWriter.

Before · Python
After · Python
records = json.load(f)
writer = csv.DictWriter(out, fieldnames=records[0].keys())
# Manca il campo "discount" che appare solo in records[2]
records = json.load(f)
all_keys = list(dict.fromkeys(k for r in records for k in r))
writer = csv.DictWriter(out, fieldnames=all_keys, restval="")
# Ogni chiave di ogni record è inclusa come colonna
Scrivere dict annidati direttamente nel CSV senza appiattirli

Problema: csv.DictWriter chiama str() sui dict annidati, producendo colonne con valori come "{'city': 'Portland'}"— repr Python grezzo, non dati reali.

Soluzione: Appiattisci prima gli oggetti annidati usando pd.json_normalize() o una funzione di appiattimento personalizzata.

Before · Python
After · Python
records = [{"id": "evt_1", "meta": {"source": "web", "region": "us-west"}}]
writer = csv.DictWriter(f, fieldnames=["id", "meta"])
writer.writerows(records)
# la colonna meta contiene: {'source': 'web', 'region': 'us-west'}
import pandas as pd
records = [{"id": "evt_1", "meta": {"source": "web", "region": "us-west"}}]
df = pd.json_normalize(records, sep="_")
df.to_csv("events.csv", index=False)
# Colonne: id, meta_source, meta_region

csv.DictWriter vs pandas — Confronto rapido

Metodo
JSON annidato
Tipi personalizzati
Streaming
Dipendenze
Richiede installazione
csv.DictWriter
✗ (flatten manuale)
✓ (riga per riga)
Nessuna
No (stdlib)
csv.writer
✓ (riga per riga)
Nessuna
No (stdlib)
pd.DataFrame.to_csv()
✗ (solo flat)
✓ (via dtypes)
pandas + numpy
pip install
pd.json_normalize() + to_csv()
✓ (via dtypes)
pandas + numpy
pip install
csv.writer + json_flatten
flatten_json
pip install
jq + csvkit (CLI)
✓ (via jq)
N/A
jq, csvkit
Installazione di sistema

Usa csv.DictWriter quando hai bisogno di zero dipendenze, il tuo JSON è piatto e lo script gira in un ambiente ristretto (container CI, funzioni Lambda, Python integrato). Usa pd.json_normalize() + to_csv() quando il JSON è annidato, hai bisogno di trasformare o filtrare i dati prima dell'esportazione, o sei già in un workflow pandas. Per i file che non stanno in memoria, combina ijson con csv.DictWriter per lo streaming a memoria costante.

Per conversioni rapide senza codice, il convertitore JSON in CSV su ToolDeck lo gestisce senza alcuna configurazione Python.

Domande frequenti

Come si converte JSON in CSV in Python senza pandas?

Usa i moduli integrati json e csv. Chiama json.load() per analizzare il file JSON in una lista di dict, estrai i fieldnames dalle chiavi del primo dict, crea un csv.DictWriter, chiama writeheader() e poi writerows(). Questo approccio ha zero dipendenze esterne e funziona in qualsiasi ambiente Python 3.x. È anche più veloce di pandas per file piccoli, poiché non c'è overhead di allocazione del DataFrame. Se gli oggetti JSON hanno chiavi inconsistenti tra i record, raccogli prima tutte le chiavi univoche con dict.fromkeys(k for r in records for k in r) prima di passarle come fieldnames per evitare colonne mancanti.

Python
import json
import csv

with open("orders.json") as f:
    records = json.load(f)

with open("orders.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=records[0].keys())
    writer.writeheader()
    writer.writerows(records)

Come si gestisce il JSON annidato nella conversione in CSV?

Gli array JSON piatti si mappano direttamente alle righe CSV, ma gli oggetti annidati devono essere appiattiti prima. Con pandas, pd.json_normalize() gestisce questo automaticamente — unisce le chiavi annidate con un separatore punto (es. "address.city"). Senza pandas, scrivi una funzione ricorsiva che percorre il dict e concatena le chiavi con un delimitatore. Per strutture profondamente annidate con più livelli, json_normalize le gestisce tutte in un solo passaggio. Il parametro sep controlla il carattere di unione tra i segmenti di chiave — il trattino basso è generalmente più sicuro del punto predefinito per import SQL e compatibilità con le formule dei fogli di calcolo.

Python
import pandas as pd

nested_data = [
    {"id": "ord_91a3", "customer": {"name": "Maria Rossi", "email": "m.rossi@esempio.it"}},
]
df = pd.json_normalize(nested_data, sep="_")
# Colonne: id, customer_name, customer_email
df.to_csv("flat_orders.csv", index=False)

Perché il mio CSV ha righe vuote tra le righe di dati su Windows?

Il modulo csv scrive terminazioni di riga \r\n per default. Su Windows, aprire il file in modalità testo aggiunge un altro \r, producendo \r\r\n — che appare come una riga vuota. La soluzione è passare sempre newline="" a open(). Questo dice a Python di non tradurre le terminazioni di riga, lasciando che il modulo csv le gestisca. Questo pattern è richiesto indipendentemente dal sistema operativo — è innocuo su macOS e Linux, ed è critico su Windows. La documentazione Python lo indica esplicitamente nella sezione del modulo csv come il modo corretto per aprire i file per la scrittura CSV.

Python
# Errato — righe vuote su Windows
with open("output.csv", "w") as f:
    writer = csv.writer(f)

# Corretto — newline="" previene il doppio \r
with open("output.csv", "w", newline="") as f:
    writer = csv.writer(f)

Come si aggiungono record JSON a un file CSV esistente?

Apri il file in modalità append ("a") e crea un DictWriter con gli stessi fieldnames. Salta writeheader() poiché la riga di intestazione esiste già. Con pandas, usa to_csv(mode="a", header=False). Assicurati che l'ordine delle colonne corrisponda al file esistente, altrimenti i dati finiranno nelle colonne sbagliate. Se non sei sicuro dell'ordine delle colonne nel file esistente, aprilo prima con csv.DictReader e leggi i fieldnames dal suo attributo fieldnames prima di creare il writer per l'aggiunta.

Python
import csv

new_records = [
    {"order_id": "ord_f4c1", "total": 89.50, "status": "shipped"},
]

with open("orders.csv", "a", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["order_id", "total", "status"])
    writer.writerows(new_records)

Qual è il modo più veloce per convertire un file JSON grande in CSV in Python?

Per file sotto i 500 MB, pd.read_json() seguito da to_csv() è l'approccio a singola chiamata più veloce — pandas usa internamente codice C ottimizzato. Per file sopra i 500 MB, usa ijson per fare lo streaming dei record JSON e scriverli nel CSV con csv.DictWriter riga per riga. Questo mantiene l'utilizzo della memoria costante indipendentemente dalla dimensione del file. Per i file NDJSON (un oggetto JSON per riga), non hai bisogno di ijson — un semplice ciclo for Python sul file handle elabora ogni riga indipendentemente e raggiunge una memoria costante senza alcuna libreria di terze parti.

Python
# Veloce per file che stanno in memoria
import pandas as pd
df = pd.read_json("large_dataset.json")
df.to_csv("large_dataset.csv", index=False)

# Streaming per file che non stanno in memoria
import ijson, csv
with open("huge.json", "rb") as jf, open("huge.csv", "w", newline="") as cf:
    records = ijson.items(jf, "item")
    first = next(records)
    writer = csv.DictWriter(cf, fieldnames=first.keys())
    writer.writeheader()
    writer.writerow(first)
    for record in records:
        writer.writerow(record)

Posso scrivere l'output CSV su stdout invece che su un file in Python?

Sì. Passa sys.stdout come oggetto file a csv.writer() o csv.DictWriter(). Questo è utile per il piping dell'output negli script shell o per il debug rapido. Con pandas, chiama to_csv(sys.stdout, index=False) oppure to_csv(None) per ottenere una stringa da stampare. Nessun file temporaneo necessario. Quando si scrive su stdout su Windows, chiama prima sys.stdout.reconfigure(newline="") per evitare il problema del doppio carriage return, poiché stdout si apre in modalità testo per default.

Python
import csv
import sys
import json

data = json.loads('[{"host":"web-1","cpu":72.3},{"host":"web-2","cpu":45.1}]')
writer = csv.DictWriter(sys.stdout, fieldnames=data[0].keys())
writer.writeheader()
writer.writerows(data)
# host,cpu
# web-1,72.3
# web-2,45.1

Strumenti correlati

MS
Maria SantosBackend Developer

Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.

PS
Priya SharmaRevisore tecnico

Priya is a data scientist and machine learning engineer who has worked across the full Python data stack — from raw data ingestion and cleaning to model deployment and monitoring. She is passionate about reproducible research, Jupyter-based workflows, and the practical engineering side of ML. She writes about NumPy, Pandas, data serialisation, and the Python patterns that make data pipelines reliable at scale.