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

URL: https://skillaria.top/it/guides/python/json-python

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.

| Funzione | Che 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

| Python | JSON |
| --- | --- |

| `dict` | oggetto `{}` |

| `list`, `tuple` | array `[]` |

| `str` | stringa |

| `int`, `float` | numero |

| `True` / `False` | `true` / `false` |

| `None` | `null` |

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.

## FAQ

**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.


---
Source: Skillaria.Top (https://skillaria.top)
