Introduzione
Quando un programma Python comunica con un’API, riceve spesso dati strutturati sotto forma di dizionari annidati. Un esempio tipico è una risposta JSON che contiene informazioni su un cliente, un ordine o una prenotazione. Anche se il formato JSON è ordinato e leggibile, i dati ricevuti potrebbero essere incompleti, avere tipi errati oppure contenere campi non necessari.
I dizionari sono particolarmente adatti per questo scenario perché permettono di accedere rapidamente ai valori tramite chiave, controllare la presenza dei campi e costruire una nuova struttura più semplice da usare nel resto dell’applicazione.
In questo tutorial realizzeremo una funzione che:
- valida i campi obbligatori di una richiesta;
- controlla il tipo e il contenuto dei valori;
- ignora eventuali campi sconosciuti;
- trasforma i dati in un formato interno coerente;
- restituisce errori comprensibili senza interrompere il programma.
Questo approccio è utile in applicazioni web, servizi REST, script di importazione e programmi che leggono dati da file JSON.
Codice completo
from datetime import datetime
def valida_e_normalizza_ordine(dati):
"""
Valida un dizionario ricevuto dall´esterno e restituisce:
- un dizionario normalizzato;
- una lista di errori.
"""
errori = []
# Controllo del tipo principale
if not isinstance(dati, dict):
return None, ["Il payload deve essere un dizionario"]
# Campi obbligatori
campi_obbligatori = ["id_ordine", "email_cliente", "totale", "articoli"]
for campo in campi_obbligatori:
if campo not in dati:
errori.append(f"Campo obbligatorio mancante: {campo}")
# Se mancano campi fondamentali, non è possibile continuare
if errori:
return None, errori
# Validazione dell´identificativo
id_ordine = dati["id_ordine"]
if not isinstance(id_ordine, int) or id_ordine <= 0:
errori.append("id_ordine deve essere un intero positivo")
# Validazione dell´email
email = dati["email_cliente"]
if not isinstance(email, str) or "@" not in email:
errori.append("email_cliente non è valida")
else:
email = email.strip().lower()
# Validazione del totale
totale = dati["totale"]
if not isinstance(totale, (int, float)) or totale < 0:
errori.append("totale deve essere un numero maggiore o uguale a zero")
else:
totale = round(float(totale), 2)
# Validazione degli articoli
articoli_ricevuti = dati["articoli"]
articoli_normalizzati = []
if not isinstance(articoli_ricevuti, list):
errori.append("articoli deve essere una lista")
else:
for posizione, articolo in enumerate(articoli_ricevuti, start=1):
if not isinstance(articolo, dict):
errori.append(
f"L´articolo {posizione} deve essere un dizionario"
)
continue
if "codice" not in articolo:
errori.append(
f"L´articolo {posizione} non contiene il codice"
)
continue
quantita = articolo.get("quantita", 1)
if not isinstance(quantita, int) or quantita <= 0:
errori.append(
f"La quantità dell´articolo {posizione} non è valida"
)
continue
articoli_normalizzati.append({
"codice": str(articolo["codice"]).strip().upper(),
"quantita": quantita
})
if errori:
return None, errori
# Costruiamo un nuovo dizionario controllato.
# I campi extra ricevuti non vengono copiati.
ordine_normalizzato = {
"id": id_ordine,
"cliente": email,
"totale_euro": totale,
"articoli": articoli_normalizzati,
"ricevuto_il": datetime.now().isoformat(timespec="seconds")
}
return ordine_normalizzato, []
# Esempio di payload ricevuto da un´API
payload = {
"id_ordine": 1042,
"email_cliente": " [email protected] ",
"totale": 89.9,
"articoli": [
{"codice": " ab-12 ", "quantita": 2},
{"codice": "xy-99"},
],
"campo_non_previsto": "valore da ignorare"
}
ordine, errori = valida_e_normalizza_ordine(payload)
if errori:
print("Dati non validi:")
for errore in errori:
print(f"- {errore}")
else:
print("Ordine accettato:")
print(ordine)
# Esempio con dati errati
payload_non_valido = {
"id_ordine": -5,
"email_cliente": "indirizzo-senza-chiocciola",
"totale": -10,
"articoli": "nessuna lista"
}
ordine, errori = valida_e_normalizza_ordine(payload_non_valido)
if errori:
print("
Errori del secondo ordine:")
for errore in errori:
print(f"- {errore}") Spiegazione
Controllare la presenza delle chiavi
Per verificare se una chiave esiste in un dizionario si usa l’operatore in:
if "email_cliente" not in dati:
print("Email mancante") Questo controllo è preferibile rispetto all’accesso diretto quando il campo potrebbe non esistere. Infatti, dati["email_cliente"] genera un’eccezione KeyError in caso di chiave assente.
Usare get() per valori opzionali
Il metodo get() consente di specificare un valore predefinito:
quantita = articolo.get("quantita", 1) Se la chiave quantita esiste, viene restituito il suo valore; altrimenti viene usato 1. Questa tecnica è utile per i campi facoltativi.
Validare prima di trasformare
Il codice non modifica direttamente il dizionario originale. Prima verifica i dati e poi costruisce ordine_normalizzato. Questa scelta evita di lasciare l’oggetto in uno stato parzialmente modificato se viene rilevato un errore.
La normalizzazione applica inoltre alcune regole pratiche:
- rimuove gli spazi dall’indirizzo email;
- converte l’email in lettere minuscole;
- arrotonda il totale a due cifre decimali;
- converte i codici articolo in maiuscolo;
- mantiene soltanto i campi necessari all’applicazione.
Dizionari annidati e liste
Il campo articoli contiene una lista di dizionari. Per elaborarla si usa un ciclo for:
for articolo in dati["articoli"]:
codice = articolo["codice"]
quantita = articolo.get("quantita", 1) Quando si lavora con strutture annidate, è importante controllare ogni livello: il payload deve essere un dizionario, articoli deve essere una lista e ogni elemento della lista deve essere un dizionario.
Best practice
- Non fidarti dei dati esterni: dati provenienti da API, form o file possono essere mancanti o errati.
- Usa messaggi di errore specifici: indicare il nome del campo facilita il debug e la correzione da parte del client.
- Non modificare il payload originale: crea un nuovo dizionario normalizzato per separare i dati esterni da quelli interni.
- Ignora i campi sconosciuti quando opportuno: questa strategia rende il codice più resistente all’evoluzione dell’API.
- Evita valori mutabili come default: non usare, per esempio,
def funzione(dati={}):. Usa inveceNonee crea il dizionario all’interno. - Usa
isinstance()per i controlli di tipo: è più chiaro e flessibile rispetto a confronti rigidi contype(). - Considera
Decimalper importi finanziari:floatè sufficiente per molti esempi, ma nei pagamenti è preferibile evitare gli errori di precisione binaria.
Riepilogo
I dizionari non servono soltanto a memorizzare coppie chiave-valore: sono uno strumento fondamentale per ricevere, controllare e trasformare dati strutturati. Usando in, get(), isinstance() e dizionari annidati possiamo costruire funzioni affidabili per gestire payload provenienti da sistemi esterni.
La tecnica principale consiste nel separare tre fasi: validazione, raccolta degli errori e normalizzazione. In questo modo il resto dell’applicazione riceve dati coerenti, indipendentemente dalla qualità del payload originale.
