Guida

JSON in Python: lettura e scrittura di file, CSV, pathlib e codifica UTF-8

Aggiornato: · Skillaria.Top

Per lavorare con JSON in Python c'è il modulo integrato json: json.load legge i dati da un file aperto, json.dump li scrive in un file, mentre json.loads e json.dumps fanno lo stesso con le stringhe. Il file si apre con with open(percorso, encoding="utf-8"), i percorsi si costruiscono comodamente con pathlib.Path, mentre le tabelle si leggono con il modulo csv.

Lavorare con JSON in Python non richiede librerie esterne: il modulo json fa parte della libreria standard e trasforma dizionari e liste in testo e viceversa. Accanto a esso servono quasi sempre altre tre cose: la funzione open con il costrutto with, il modulo pathlib per i percorsi e il modulo csv per le tabelle. Qui sotto trovate tutto in ordine, con codice funzionante e le trappole più comuni.

La funzione open in Python: modalità e codifica

Ogni file di testo si apre con la funzione integrata open(path, mode, encoding=...). Restituisce un oggetto file, con i metodi read, readline, write, che si può scorrere in un ciclo riga per riga.

ModalitàChe cosa fa
"r"Lettura (predefinita). Se il file non esiste — FileNotFoundError
"w"Scrittura. Il file viene creato oppure svuotato già al momento dell'apertura
"a"Aggiunta in fondo, il contenuto esistente non viene toccato
"x"Creazione di un file nuovo; se esiste già — FileExistsError
"b"Aggiunta alla modalità: lavoro con i byte ("rb", "wb")

A che cosa serve with

Il file va chiuso: altrimenti i dati potrebbero non arrivare sul disco e il sistema tiene occupato il descrittore. Il costrutto with chiude il file automaticamente, anche se nel blocco si verifica un errore.

with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("Prima riga\n")
    f.write("Seconda riga\n")

with open("notes.txt", encoding="utf-8") as f:
    for line in f:
        print(line.rstrip("\n"))

Scorrere il file in un ciclo ne legge una riga alla volta senza caricarlo tutto in memoria, quindi così si possono elaborare anche log di gigabyte. Il metodo f.read() restituisce tutto il contenuto in una stringa, f.readlines() una lista di righe.

Come aggiungere una riga a un file senza cancellare il resto

L'errore più comune dei principianti è aprire il file in modalità "w" per «aggiungere» una riga. La modalità "w" svuota il file nel momento stesso dell'apertura, prima ancora del primo write. Per aggiungere in fondo si usa "a", per essere sicuri di non sovrascrivere un file altrui — "x".

with open("log.txt", "a", encoding="utf-8") as f:
    f.write("nuova voce\n")

Attenzione: write non aggiunge da solo il ritorno a capo, il "\n" va scritto esplicitamente. Si può anche usare print(..., file=f), che aggiunge l'a capo al posto vostro.

Codifica UTF-8: da dove arrivano i caratteri illeggibili

Fino a Python 3.14 la funzione open() senza il parametro encoding usava la codifica del sistema. Su Linux e macOS è UTF-8, su Windows con impostazioni regionali in cirillico è cp1251. Uno script che funzionava bene sul server, sul portatile del collega stampava «Р•РґР°» al posto di «Cibo» oppure si interrompeva con UnicodeDecodeError.

In Python 3.15, secondo la PEP 686, la modalità UTF-8 è attiva di default: open() senza encoding legge e scrive in UTF-8 su tutti i sistemi operativi. Per tornare al comportamento precedente si può usare la variabile d'ambiente PYTHONUTF8=0 oppure il flag -X utf8=0, mentre per chiedere esplicitamente la codifica di sistema si usa encoding="locale".

import sys
print(sys.flags.utf8_mode)   # 1 in Python 3.15

open("legacy.txt", encoding="locale")     # codifica di sistema
open("old_excel.csv", encoding="cp1251")  # file salvato in cp1251

L'altra faccia della medaglia: un vecchio CSV di Excel o di uno script su Windows, salvato in cp1251, adesso verrà letto come UTF-8 e genererà UnicodeDecodeError. Si risolve leggendolo con encoding="cp1251" e risalvandolo in UTF-8. La buona pratica resta la stessa: indicate sempre encoding in modo esplicito, così il codice si comporta allo stesso modo su qualsiasi versione.

pathlib in Python: percorsi come oggetti

Unire i percorsi come stringhe ("data" + "/" + name) è scomodo e si rompe su sistemi diversi. Il modulo pathlib offre la classe Path, in cui i percorsi si uniscono con l'operatore / e l'oggetto ha proprietà chiare.

from pathlib import Path

data = Path("data")
csv_path = data / "expenses.csv"

print(csv_path.name)    # expenses.csv
print(csv_path.stem)    # expenses
print(csv_path.suffix)  # .csv
print(csv_path.parent)  # data
print(csv_path.exists())

data.mkdir(parents=True, exist_ok=True)
(data / "hello.txt").write_text("Ciao", encoding="utf-8")
print((data / "hello.txt").read_text(encoding="utf-8"))

for p in data.glob("*.csv"):
    print(p)

I metodi read_text e write_text sostituiscono la coppia open + with per i file piccoli. Un oggetto Path si può passare sia a open, sia a json, sia a csv: tutti lo accettano. Per ottenere un percorso relativo allo script, e non alla cartella corrente, si usa Path(__file__).parent / "data".

Dalla versione 3.14 Path ha i metodi copy() e move(), quindi per una semplice copia non serve più shutil. Nella stessa versione la libreria standard ha ricevuto il modulo compression.zstd per la compressione Zstandard (PEP 784).

JSON in Python: load, dump, loads, dumps

JSON è un formato di scambio dati in testo: lo restituiscono le API, lo si usa per configurazioni e report. Il modulo json sa fare due operazioni: serializzazione (oggetto Python → testo JSON) e deserializzazione (testo → oggetto). I metodi sono quattro, e la confusione tra loro è una domanda frequente.

FunzioneChe cosa fa
json.dumps(obj)Oggetto → stringa JSON
json.loads(s)Stringa JSON → oggetto
json.dump(obj, f)Oggetto → scrittura in un file aperto
json.load(f)Lettura di JSON da un file aperto

Per ricordarlo: la s finale sta per string — si lavora con una stringa; senza s si lavora con un file.

Corrispondenza dei tipi

PythonJSON
dictoggetto {}
list, tuplearray []
strstringa
int, floatnumero
True / Falsetrue / false
Nonenull

Una tupla, letta di nuovo, diventerà una lista; i set, le date e Decimal il modulo non li conosce e solleva TypeError: Object of type ... is not JSON serializable.

Come salvare JSON in un file

import json
from pathlib import Path

report = {
    "month": "2026-10",
    "total": 15230.5,
    "categories": {"cibo": 8200, "trasporti": 2100},
    "tags": ["casa", "lavoro"],
}

path = Path("data/report.json")
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as f:
    json.dump(report, f, ensure_ascii=False, indent=2)

Il parametro ensure_ascii=False conserva i caratteri accentati così come sono. Senza di esso nel file comparirebbe "cibo" con sequenze di escape al posto dei caratteri: è un JSON corretto, ma illeggibile per una persona. indent=2 produce una formattazione con rientri.

Come leggere JSON da un file

with open("data/report.json", encoding="utf-8") as f:
    data = json.load(f)

print(data["total"])               # 15230.5
print(data["categories"]["cibo"])  # 8200

Il risultato è un normale dizionario o una lista, con cui si lavora con gli strumenti abituali. Se il file non è JSON o è troncato, si ottiene l'eccezione json.JSONDecodeError con il numero di riga e la posizione: va intercettata quando i dati arrivano dall'esterno.

Analisi di una stringa JSON e stampa leggibile

import json

raw = '{"name": "Anna", "skills": ["python", "sql"], "active": true}'
user = json.loads(raw)
print(user["skills"][0])   # python

print(json.dumps(user, ensure_ascii=False, indent=2, sort_keys=True))

sort_keys=True ordina le chiavi in output, utile per confrontare i file in git. Per formattare velocemente un file dal terminale c'è l'utilità integrata: python -m json.tool data/report.json.

Anche la risposta di un'API HTTP è una stringa JSON. La libreria requests la analizza con il metodo response.json(), che internamente usa lo stesso json.loads.

Trappola: le chiavi JSON sono sempre stringhe

d = {1: "gennaio", 2: "febbraio"}
back = json.loads(json.dumps(d))
print(back == d)   # False
print(back)        # {'1': 'gennaio', '2': 'febbraio'}
# back[1] -> KeyError, serve back["1"]

fixed = {int(k): v for k, v in back.items()}

In JSON le chiavi di un oggetto possono essere solo stringhe, quindi le chiavi numeriche dopo il ciclo di serializzazione diventano stringhe. Riportatele al tipo originale in lettura.

Tipi personalizzati: date e Decimal

import json
from datetime import date

def default(obj):
    if isinstance(obj, date):
        return obj.isoformat()
    raise TypeError(f"impossibile serializzare {type(obj).__name__}")

print(json.dumps({"day": date(2026, 10, 1)}, default=default))
# {"day": "2026-10-01"}

La funzione passata al parametro default viene chiamata per ogni oggetto sconosciuto. Nei casi complessi si può ereditare da json.JSONEncoder e ridefinire il metodo default.

Come aggiungere un elemento a un array JSON

Un JSON non si può estendere in fondo come un log di testo: il file va letto, modificato e riscritto per intero.

path = Path("data/items.json")
items = json.loads(path.read_text(encoding="utf-8")) if path.exists() else []
items.append({"id": len(items) + 1, "title": "nuovo"})
path.write_text(json.dumps(items, ensure_ascii=False, indent=2), encoding="utf-8")

Se i record sono molti e si aggiungono di continuo, è più comodo il formato JSON Lines: un oggetto JSON per riga, e il file si apre in modalità "a".

CSV in Python: il modulo csv

CSV è una tabella in un file di testo: le righe sono i record, i valori sono separati da virgole. Il modulo csv gestisce correttamente le virgolette, le virgole dentro i valori e gli a capo, cosa che il semplice split(",") non fa.

import csv

with open("data/expenses.csv", newline="", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    total = 0.0
    for row in reader:
        total += float(row["amount"])
        print(row["date"], row["category"], row["amount"])
print("Totale:", total)

csv.DictReader trasforma ogni riga in un dizionario secondo l'intestazione del file. Tutti i valori sono stringhe: numeri e date vanno convertiti a mano (float, int, date.fromisoformat). Un file con il separatore «;» (così salva Excel nelle impostazioni italiane) si legge con il parametro delimiter=";".

rows = [
    {"date": "2026-10-01", "category": "cibo", "amount": 450},
    {"date": "2026-10-02", "category": "trasporti", "amount": 60},
]
with open("data/out.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["date", "category", "amount"])
    writer.writeheader()
    writer.writerows(rows)

In scrittura è obbligatorio newline="": senza di esso su Windows tra le righe compaiono righe vuote. Se il file verrà aperto in Excel, usate encoding="utf-8-sig": il marcatore BOM aiuta Excel a riconoscere l'UTF-8.

Da CSV a JSON: esempio completo

import csv
import json
from collections import defaultdict
from pathlib import Path

src = Path("data/expenses.csv")
dst = Path("data/report.json")

by_category = defaultdict(float)
with src.open(newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        by_category[row["category"]] += float(row["amount"])

report = {"total": sum(by_category.values()), "categories": dict(by_category)}
dst.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
print(dst.read_text(encoding="utf-8"))

È il nucleo di qualsiasi script di report: ingresso da CSV, aggregazione in un dizionario, uscita in JSON.

Errori tipici

  • FileNotFoundError — il percorso è relativo e viene calcolato dalla cartella di avvio, non dallo script. Usate Path(__file__).parent.
  • Il file si è svuotato — è stato aperto in "w" invece che in "a".
  • UnicodeDecodeError — il file ha un'altra codifica; indicatela esplicitamente (cp1251, utf-8-sig).
  • TypeError: Object of type set is not JSON serializable — convertite in lista oppure usate default.
  • KeyError dopo il caricamento di un JSON — le chiavi numeriche sono diventate stringhe.
  • Il vostro file si chiama json.py o csv.py — copre il modulo standard, e import json importa proprio lui.

Conclusioni

Per il JSON in Python basta la libreria standard: json.load/json.dump per i file, loads/dumps per le stringhe, ensure_ascii=False e indent per un output leggibile. I file si aprono con with e con la codifica esplicita, i percorsi si costruiscono con pathlib, le tabelle si leggono con csv.DictReader. Nel corso di Python questo argomento è trattato in pratica: il tracker delle spese legge le uscite da un CSV e salva il report in JSON.

Domande frequenti

In che cosa json.load si differenzia da json.loads?

json.load legge il JSON da un oggetto file aperto, mentre json.loads analizza una stringa. Allo stesso modo json.dump scrive in un file, mentre json.dumps restituisce una stringa.

Come salvare i caratteri accentati in JSON senza sequenze \u?

Passate ensure_ascii=False a json.dump o json.dumps e aprite il file con encoding="utf-8".

Serve installare una libreria per il JSON in Python?

No, il modulo json è incluso nella libreria standard: basta scrivere import json.

Si possono salvare in JSON una data o un set?

Non direttamente: vanno convertiti, per esempio la data in stringa ISO con isoformat e il set in lista, oppure va passata una funzione al parametro default.

Come leggere un JSON troppo grande per la memoria?

Il json.load standard legge il file per intero; per volumi grandi conservate i dati in formato JSON Lines e analizzate una riga alla volta con json.loads.

Impara l’argomento in video

Questo argomento è trattato in modo approfondito, con pratica, nel corso «Python 3.15: от новичка до профи».

Vai al corso