Corso Python / Leggere, scrivere e validare dati JSON
JSON, JavaScript Object Notation, è un formato testuale per scambiare dati strutturati come oggetti, array, stringhe e numeri. Nel modulo json, load e dump operano su file, mentre loads e dumps operano su stringhe; il formato non conserva automaticamente ogni tipo e comportamento degli oggetti Python.
Associare i valori JSON ai tipi Python
| JSON | Python dopo la lettura standard |
|---|---|
| object | dict |
| array | list |
| string | str |
| Numero intero / numero con decimali | int / float |
| true / false / null | True / False / None |
In JSON, le stringhe e i nomi delle proprietà richiedono doppi apici. Una rappresentazione Python con apici singoli, True o None non è automaticamente JSON valido; non aggiungere commenti o virgole finali supponendo che il lettore li accetti.
La trasformazione non è sempre reversibile rispetto ai tipi: una tupla viene normalmente scritta come array e riletta come lista. Le chiavi degli oggetti JSON sono stringhe, perciò un dizionario con chiavi intere può cambiare dopo un ciclo di salvataggio e lettura. Date, Decimal e istanze personalizzate richiedono una politica di conversione esplicita. La documentazione ufficiale di json riporta le conversioni e le opzioni del modulo.
Salvare un valore Unicode e rileggerlo
Usa una cartella di esercizio separata: il programma sostituisce expenses-demo.json a ogni avvio. Salva il codice completo come json_demo.py, in UTF-8, ed esegui python json_demo.py nel terminale con l’interprete del progetto selezionato. Fuori dall’ambiente virtuale su macOS o Ubuntu usa python3 quando è il nome dell’interprete scelto. Non occorrono librerie esterne.
Il nome café contiene un carattere non ASCII, così puoi distinguere la rappresentazione leggibile dalle sequenze di escape. Il codice conserva KRW come etichetta della valuta e 8000 come importo intero: non effettua cambi di valuta.
import json
from pathlib import Path
data = {"currency": "KRW", "expenses": [{"name": "café", "amount": 8000}]}
path = Path("expenses-demo.json")
path.write_text(json.dumps(data, ensure_ascii=False, indent=2, allow_nan=False), encoding="utf-8")
loaded = json.loads(path.read_text(encoding="utf-8"))
amount = loaded["expenses"][0]["amount"]
if type(amount) is not int or amount < 0:
raise ValueError("invalid amount")
print(loaded["expenses"][0]["name"])
print(amount)
Il risultato previsto è:
café
8000
Dumps crea prima una stringa JSON. Write_text la salva con codifica UTF-8; read_text la rilegge e loads ricostruisce i dati Python. Queste operazioni usano il percorso relativo alla cartella di lavoro, come negli esempi sui file di testo e le codifiche.
| Funzione | Ingresso e risultato |
|---|---|
| load(file) | Legge un documento da un file e restituisce dati Python |
| loads(text) | Interpreta una stringa JSON e restituisce dati Python |
| dump(obj, file) | Scrive la rappresentazione JSON su un file aperto |
| dumps(obj) | Restituisce una stringa JSON senza creare un file |
Separare parsing e validazione dei dati
Un documento sintatticamente valido può comunque avere chiavi mancanti, expenses di tipo sbagliato, un array vuoto o un importo fuori dai limiti accettati. L’esempio conosce già la struttura che ha appena prodotto e controlla un solo importo; un importatore generale deve verificare prima anche contenitore, chiavi e singoli record.
Il controllo type(amount) is int ammette esattamente gli interi ed esclude bool. In Python bool è una sottoclasse di int, quindi isinstance(amount, int) da solo accetterebbe anche True. Qui il contratto vuole un importo intero non negativo: -1, True, 8000.0 e "8000" vengono rifiutati, mentre 0 è ammesso. Altri programmi possono consentire tipi diversi, ma devono dichiararlo.
allow_nan=False rifiuta NaN e Infinity durante la serializzazione. Non è un’opzione generale per validare automaticamente ogni documento in lettura: il lettore standard può accettare costanti numeriche non standard e oggetti con proprietà duplicate. Per un’interfaccia rigorosa definisci una politica anche per questi casi, usando per esempio parse_constant e object_pairs_hook secondo le regole applicative.
L’accesso diretto a expenses e al primo elemento può generare KeyError o IndexError se manca la struttura prevista; questi errori non sono equivalenti a JSONDecodeError. Non nascondere tutte le eccezioni dietro un risultato vuoto: identifica se il problema riguarda sintassi, struttura o valore.
Controllare caratteri, file e prove di errore
ensure_ascii=False conserva café come testo leggibile invece di rappresentare il carattere accentato con un escape Unicode. Non determina la codifica del file: encoding="utf-8" resta una scelta separata. Anche la versione con escape può rappresentare lo stesso dato; indent cambia la disposizione del testo, non il significato dei valori.
- Esegui l’originale e apri il JSON generato come testo. Controlla café, l’importo 8000 e l’indentazione.
- In una copia del sorgente cambia soltanto il valore amount a -1, poi a True, usando la sintassi Python. Ogni versione deve sollevare ValueError con invalid amount; nel documento JSON corrispondente il booleano viene scritto true.
- Ripristina 8000 e verifica nuovamente il risultato. Modificare soltanto il file JSON e riavviare il programma non prova il rifiuto del valore modificato, perché il programma lo riscrive prima di leggerlo.
Un testo con una virgola finale o con virgolette errate provoca JSONDecodeError: correggi il formato e usa il parser json, senza passare a eval. Un file può inoltre non esistere o avere una codifica inattesa, problemi da distinguere dalla sintassi JSON. Per dati non attendibili limita anche le dimensioni dell’ingresso e valida la struttura prima di avviare elaborazioni costose.




