Vai al contenuto

JSON

In questo capitolo, per lavorare con il formato di testo JSON, introdurremo prima una struttura dati che completa la nostra conoscenza sulle sequenze: i dizionari!

Questi saranno lo strumento tramite il quale lavoreremo poi con i dati su file e il modulo json.

Dizionari

I dizionari (dict) sono un tipo Python predefinito, sequenza, mutabile, non ordinato (e non ordinabile) che contiene elementi (items) formati ognuno da una coppia chiave (key): valore (value).

Per dichiarare gli elementi di un dizionario si utilizzano le parentesi graffe e questo ci fa capire che i tipi predefiniti sono finiti, perché sono finiti i tipi di parentesi esistenti 😄

Vediamo con un esempio il concetto:

prefisso = { "IT": 39, "ES": 34, "DE": 49}

In questo semplice esempio abbiamo memorizzato i prefissi internazionali di alcuni stati: data la chiave (il nome dello stato) si può visualizzare il prefisso corrispondente (il "valore"), ricorrendo però alle parentesi quadre:

print( prefisso["IT"] ) # scrive 39

Per completezza, ricordo che è possibile verificare il tipo della variabile con il comando type()

type(prefisso)    # vale <class 'dict'>

e che è possibile creare un dizionario vuoto semplicemente utilizzando le parentesi graffe: essendo i dizionari un tipo mutabile sarà sempre possibile aggiungervi dati successivamente.

dizionarioVuoto = {}

Torniamo all'esempio del dizionario dei prefissi e vediamo come è semplice inserire un nuova coppia chiave: valore nel dizionario. Occhio all'utilizzo delle parentesi, che in questo caso sono le quadre:

prefisso["FR"] = 34
print(prefisso)
{ "IT": 39 , "ES": 34 , "DE": 49 , "FR": 34 }

Come vedete ci sono 2 stati ("ES" e "FR") che hanno lo stesso valore, lo stesso prefisso. Questo è assolutamente possibile nei dizionari. I dizionari però, non ammettono valori duplicati nelle chiavi, quindi non è possibile inserire due chiavi uguali per valori diversi. Da questa considerazione si capisce anche che le chiavi di un dizionario devono essere per forza tipi immutabili (ad esempio numeri, stringhe, tuple, etc… solitamente interi o stringhe) mentre per i valori si può dare spazio alla fantasia.

L'inserimento dei dati dipende dall'esistenza o meno della chiave nel dizionario:

# se la chiave non esiste, viene aggiunta una nuova coppia nel dizionario
prefisso["UK"] = 44
# se la chiave è già presente, viene modificato il valore in essa
prefisso["FR"] = 33
print(prefisso)
{ "IT": 39, "ES": 34, "DE": 49, "FR": 33, "UK": 44 }

I dizionari non possono essere ordinati!!!

Non è possibile ordinare i valori presenti in un dizionario. Per procedere ad una ricerca bisogna fare affidamento solo sulle chiavi!!!

Lavorare con i dizionari

Per le nostre considerazioni, partiremo analizzando il dizionario "prefisso" già definito:

print(prefisso)
{ "IT": 39, "ES": 34, "DE": 49, "FR": 33, "UK": 44 }


Come sappiamo, è possibile interrogare una sequenza per sapere se un elemento è presente in essa. Nei dizionari l'istruzione IN funziona con le chiavi:

"IT" in prefisso    # vale True
"US" in prefisso    # vale False

Quindi, il seguente codice:

if "IT" in prefisso:
    print("il prefisso \"IT\" è presente e vale", prefisso["IT"])
else:
    print("il prefisso \"IT\" non è presente")

scriverà:

il prefisso "IT" è presente e vale 39


L'istruzione for...in che ben conosciamo, scorre le chiavi dei dizionari:

for chiave in prefisso:
    print(chiave)
IT
ES
DE
FR
UK

Questo significa che è semplicissimo usare il FOR...IN per ottenere le chiavi e visualizzare dunque i valori contenuti nel dizionario, con la sintassi dizionario[chiave] = valore:

for chiave in prefisso:
    print( "Prefisso", chiave, ":", prefisso[chiave] )
Prefisso IT : 39
Prefisso ES : 34
Prefisso DE : 49
Prefisso FR : 33
Prefisso UK : 44


I dizionari supportano inoltre alcuni metodi aggiuntivi tipici della classe dict. Dopo la tabella riassuntiva trovate alcuni esempi esplicativi.

Metodo Descrizione
dic.keys() Restituisce una sequenza speciale (tipo una lista) che contiene tutte le chiavi del dizionario dic, aggiornate in tempo reale.
dic.values() Restituisce una sequenza speciale (tipo una lista) che contiene tutti i valori del dizionario dic, aggiornati in tempo reale.
dic.get(key, default) Restituisce il valore corrispondente a key, oppure default se non esiste.
Se default è nullo, non restituisce nulla.
dic.pop(key, default) Restituisce il valore corrispondente a key e lo elimina dal dizionario. Se non presente restituisce default.
Se non presente e default non impostato, genera un errore chiamato KeyError.
dic.copy() Clona dic
dic.clear() Cancella dic, ovvero rimuove tutti i suoi elementi

Esempio 1: keys

chiavi = prefisso.keys()
print(chiavi)    # scrive dict_keys( ["IT","ES","DE","FR","UK"] )... simile ad una lista, vedete?
for k in chiavi:
    print(k)
IT
ES
DE
FR
UK
prefisso["PR"] = 25 # aggiungo un elemento al dizionario
print(chiavi)
dict_keys( ["IT","ES","DE","FR","UK","PR"] ) # valori aggiornati

Esempio 2: values

valori = prefisso.values()
print(valori)     # scrive dict_values( [39, 34, 49, 33, 44] ) ... simile ad una lista, vedete?
for v in valori:
    print(k)
39
34
49
33
44
prefisso["PR"] = 25 # aggiungo un elemento al dizionario
print(valori)
dict_values( [39, 34, 49, 33, 44, 25] ) # valori aggiornati

Esempio 3: get

# prefisso vale { "IT": 39, "ES": 34, "DE": 49, "FR": 33, "UK": 44 }

prefisso.get("US")      # la chiave "US" non c'è. Non ritorna nulla
prefisso.get("US", 0)   # "US" chiave non presente, ritorna 0
prefisso.get("IT")      # "IT" chiave presente, ritorna il valore 39
prefisso.get("IT", 0)   # essendo "IT" presente, ritorna comunque 39

Esempio 4: pop

# prefisso vale { "IT": 39, "ES": 34, "DE": 49, "FR": 33, "UK": 44 }

prefisso.pop("IT")    # chiave "IT" presente, ritorna il valore 39
print(prefisso)       # scrive { "ES": 34, "DE": 49, "FR": 33, "UK": 44 }
                      # la coppia "IT":39 è stata eliminata dal dizionario

prefisso.pop("US")    # la chiave "US" non c'è. ERRORE
KeyError
prefisso.pop("US", 0) # valore di default impostato a 0, ritorna 0

Tip

con un valore di default impostato pop() non da mai errore!!!

Spero che osservando gli esempi sia tutto chiaro sul funzionamento dei dizionari e sulle funzioni disponibili per essi. Mi raccomando di provare voi stessi a riprodurre gli esempi per meglio capire quello che succede. E poi di provare a fare gli esercizi qui sotto :)

Esercizi sui dizionari


Esercizio 801

Creare un dizionario vuoto, chiamato DatiPersonali. Chiedere all'utente di inserire i propri dati personali (nome, cognome, indirizzo, numero civico, città, provincia, CAP) e inserirli nel dizionario con il tipo di dato come chiave (ad esempio "nome") e il dato inserito dall'utente come valore.

Visualizzare i dati contenuti nel dizionario tramite un ciclo for..in sulle chiavi del dizionario.


Esercizio 802

Definire un dizionario vuoto chiamato "quadrati" e inserirvi all'interno una serie di valori tramite codice: le chiavi saranno i numeri interi da 1 a 100 e i valori saranno i quadrati delle chiavi.

Visualizzare tutto il dizionario scrivendo per ogni riga: "chiave" al quadrato = "valore"


Esercizio 803

Definire un dizionario vuoto chiamato "dispari" e inserirvi all'interno una serie di valori tramite codice: le chiavi saranno i numeri da 0 a 99 e i valori saranno i primi 100 numeri dispari. Mostrare il dizionario così ottenuto, visualizzando per ogni elemento la stringa "chiave: key, numero dispari abbinato: value".

Chiedere all'utente di inserire un numero intero e se esso è una chiave del dizionario, visualizzare il numero corrispondente. Altrimenti visualizzare la scritta "chiave non presente nel dizionario".


Esercizio 804

Definire il dizionario vuoto chiamato "pari" e inserirvi all'interno una serie di valori tramite codice: le chiavi saranno i numeri da 1 a 50 e i valori saranno i primi 50 numeri pari. Mostrare il dizionario così ottenuto, visualizzando per ogni elemento la stringa "chiave: key, numero pari abbinato: value".

Chiedere all'utente di inserire un numero intero e se esso è una chiave del dizionario, eliminare la coppia chiave:valore corrispondente. Altrimenti visualizzare la scritta "chiave non presente nel dizionario".


Esercizio 805

Definire un dizionario, inizialmente vuoto, chiamato "studentiDaInterrogare". All'inizio va riempito con una serie di coppie dove la chiave sono i numeri interi che vanno da 1 in avanti (la posizione sul registro) e il valore è il cognome dello studente.

Definire la lista "interrogati", inizialmente vuota. Estrarre 5 numeri casuali fra 1 e il numero di studenti da interrogare. Se il numero estratto è presente come chiave del dizionario, eliminare da esso l'elemento che corrisponde a quella chiave e inserire quel valore nella lista "interrogati".

Al termine delle 5 estrazioni visualizzare la lista "interrogati" e il dizionario "studentiDaInterrogare".


Esercizio 806

Definire un dizionario vuoto chiamato "Magazzino", che sarà riempito con elementi tali che le chiavi rappresentano il prodotto presente in esso e i valori il numero di pezzi dello stesso. Inserire al suo interno i seguenti prodotti: 25 bastoni, 138 scaffali, 2000 scatole, 50 attaccapanni.

Visualizzare il contenuto del magazzino.

Chiedere all'utente di selezionare un prodotto; se il prodotto è presente, visualizzare il numero di elementi di quel prodotto presenti nel magazzino e permettere all'utente di indicare quanti ne desidera. Il magazzino deve essere aggiornato togliendo dal magazzino i prodotti acquistati dall'utente.
Ad esempio, se l'utente seleziona scaffali, si visualizzi una scritta tipo: "Presenti 25 scaffali. Quanti vuole acquistarne? Se l'utente digita 30, ovviamente il programma insulta l'utente. Se digita, ad esempio, 5, si visualizza una frase tipo "venduti 5 scaffali" e il magazzino viene aggiornato al nuovo numero di scaffali presenti (25 erano, 5 sono stati venduti... 20!!!)

Visualizzare il magazzino, elencando i prodotti rimasti. Per ognuno dei prodotti rimasti, togliere 1 pezzo. Visualizzare di nuovo il magazzino.


Esercizio 807

Definire un dizionario chiamato "cartellaTombola" e riempirlo con 5 numeri interi random fra 1 a 90 diversi fra loro. Essi saranno la chiave dell'elemento inserito, mentre il valore sarà per tutti il booleano False. Visualizzare le chiavi del dizionario "cartellaTombola".

Ad esempio potrebbe essere generato un dizionario tipo { 23: False, 45: False, 56: False, 78: False, 90:False}.

Ripetere 10 volte l'estrazione casuale di un numero compreso fra 1 e 90. Se il numero è una chiave della cartella, modificare a True il valore corrispondente. Alla fine delle 10 estrazioni verificare quanti e quali numeri della cartella sono stati estratti.


Esercizio 808

Permettere all'utente di inserire una parola e creare con questa il dizionario delle frequenze delle lettere. Ad esempio, data una stringa "ababcc", otterremo in risultato {"a": 2, "b": 2, "c": 2, "d": 0…...}.

Visualizzare alla fine la parola e tutti gli elementi del dizionario. Analizzando il dizionario trovare la lettera più frequente nella parola inserita. Visualizzare infine solo gli elementi del dizionario che hanno frequenza positiva.


Esercizio 809

Creare un dizionario ItalianoInglese e inserire una serie di termini con la parola italiana come chiave e la corrispondente traduzione inglese come valore. Ad esempio alla chiave "cane" viene abbinato il valore "dog".

Data una qualunque stringa da parte dell'utente procedere a visualizzare la stringa "tradotta" sostituendo tutte le parole trovate presenti nel dizionario con la corrispondente parola inglese.


Esercizio 810

Definire un dizionario, chiamato "voti", inizialmente vuoto. In esso dovranno essere aggiunti i voti di tutte le vostre materie, in modo tale che il nome della materia sia la chiave ed il voto corrispondente il valore. Visualizzare il dizionario con un ciclo, visualizzando in ogni riga la chiave (il nome della materia) e il valore corrispondente (il voto).

  • Calcolare la media aritmetica dei voti e visualizzarla.
  • Visualizzare la materia con il voto più alto (la materia preferita).
  • Visualizzare i nomi di tutte le materie insufficienti (ahi ahi ahi...)
  • Dato un nome di materia da parte dell'utente, visualizzare il voto se la materia è presente nel dizionario, oppure scrivere "materia non presente".

Esercizio 811

Definire un dizionario chiamato "ortofrutta" inizialmente vuoto. In esso dovranno essere inserite una serie di informazioni, che rappresentano ognuna un prodotto per un negozio di frutta e verdura, organizzate in questo modo:

  • la chiave deve essere il nome del prodotto (es: "pere", "patate", ecc…)
  • il valore sarà una tupla di 2 informazioni:
    • un numero per la quantità presente (in chilogrammi)
    • un numero per il costo al chilo del prodotto

Inserire tramite codice una serie di almeno 6 prodotti da vendere all'ortofrutta e procedere ad una visualizzazione come indicato:

prodotto: NOME
quantità: VALORE kg
costo: VALORE euro al chilo
(riga vuota)

Chiedere all'utente di inserire un prodotto. Se esso è presente nel dizionario, chiedere quanti chili di esso se ne vogliono acquistare. Se il numero inserito è minore o uguale alla quantità presente, diminuire la quantità attuale dei chili venduti e visualizzare all'utente il costo totale del prodotto acquistato.


Esercizio 812

Definire un dizionario chiamato "squadra" inizialmente vuoto. In esso dovranno essere inserite una serie di informazioni, che rappresentano ognuna un giocatore della squadra, organizzate in questo modo:

  • la chiave deve essere il numero di maglia del giocatore
  • il valore sarà una tupla di 2 informazioni:
    • una stringa per il nome del giocatore
    • un carattere per il ruolo ('P per portiere, 'D' per difensore, 'C' per centrocampista, 'A' per attaccante)

Inserire tramite codice una serie di almeno 6 giocatori nella squadra e procedere ad una visualizzazione come indicato:

numero: NUMERO
nome: NOME
ruolo: RUOLO
(riga vuota)

Definire un nuovo dizionario chiamato "ruoli" ove vi saranno inseriti 4 elementi: le chiavi saranno i ruoli "P", "D", "C", "A" possibili per i giocatori della squadra, i valori saranno il numero di giocatori per quel ruolo presenti nel dizionario "squadra".

Visualizzare il dizionario così completato.


Esercizio 813

Definire un dizionario chiamato "rubrica" inizialmente vuoto. In esso dovranno essere inserite una serie di informazioni, che rappresentano ognuna un contatto della rubrica, organizzate in questo modo:

  • la chiave deve essere il nickname del contatto (ad esempio: "mamma", "prof", etc...)
  • il valore sarà una tupla di 3 informazioni:
    • una stringa per il numero di telefono del contatto
    • una stringa per l'indirizzo di residenza
    • un intero per l'anno di nascita

Inserire tramite codice una serie di almeno 5 contatti nella rubrica e procedere ad una visualizzazione come indicato:

nome: NOME
telefono: NUMERO
indirizzo: INDIRIZZO
anno di nascita: ANNO
(riga vuota)

Riempire una lista con i nickname dei contatti minorenni, visualizzare l'elenco e dire quanti sono in totale. Permettere all'utente di inserire un nick e se presente visualizzare il suo numero di telefono, altrimenti visualizzare la scritta "CONTATTO NON PRESENTE".


Esercizio 814

Definire un dizionario chiamato "pazienti" inizialmente vuoto. In esso dovranno essere inserite una serie di informazioni, che rappresentano ognuna una persona in cura da un dietologo, organizzate in questo modo:

  • la chiave deve essere il nome della persona
  • il valore sarà una tupla di 4 informazioni:
    • peso (in kg)
    • altezza (in cm)
    • età (in anni)
    • sesso (M' oppureF')

Inserire tramite codice una serie di almeno 5 persone e procedere ad una visualizzazione come indicato:

nome: NOME
peso: VALORE kg
altezza: VALORE cm
maschio di TOT anni (oppure femmina di... a seconda del valore)
(riga vuota)
  1. Visualizzare i nomi di tutte le femmine.
  2. Contare il numero di minorenni più alti di 180 cm e visualizzare i loro nomi.
  3. Visualizzare il nome del maschio e della femmina che pesano di più.

Modulo JSON

JSON (JavaScript Object Notation) è un formato di testo per rappresentare dati strutturati. È leggibile dagli esseri umani e facilmente elaborabile dai programmi. Lo trovi ovunque: API web, file di configurazione, scambio dati tra dispositivi (ad esempio ESP32 e un server Flask!).

Un documento JSON assomiglia a un dizionario Python:

{
  "nome": "Andrea",
  "età": 30,
  "attivo": true,
  "sensori": ["temperatura", "umidità"],
  "ultima_lettura": null
}

Attenti, però! Si scrive come un dizionario, ma è una semplice stringa!!! Per passare dalla stringa JSON al dizionario Python occorre il modulo json!

import json

Il modulo appartiene alla libreria standard, non occorre installare nulla!!!

Le sue funzioni principali sono:

Funzione Cosa fa
json.dumps(diz) dict Python → stringa JSON
json.loads(string) Stringa JSON → dict Python

Vediamo alcuni esempi per capire meglio:

json.dumps()
import json

dati = {
    "stazione": "ESP32-01",
    "temperatura": 23.5,
    "umidità": 60,
    "attivo": True
}

testo_json = json.dumps(dati)
print(testo_json)
# {"stazione": "ESP32-01", "temperatura": 23.5, "umidità": 60, "attivo": true}
Opzioni utili di dumps()
# Indentazione leggibile
print(json.dumps(dati, indent=2))

# Ordinare le chiavi
print(json.dumps(dati, sort_keys=True))

# Gestire caratteri non ASCII (es. lettere accentate)
print(json.dumps(dati, ensure_ascii=False, indent=2))

Quando converti i dati da un dizionario Python a JSON e viceversa, è bene tenere a mente la seguente tabella delle corrispondenze:

Tipi Python Tipi JSON
dict object {}
list, tuple array []
str string ""
int, float number
True / False true / false
None null
Da JSON a Python: json.loads()
import json

testo = '{"stazione": "ESP32-01", "temperatura": 23.5, "attivo": true}'

dati = json.loads(testo)
print(type(dati))          # <class 'dict'>
print(dati["stazione"])    # ESP32-01
print(dati["temperatura"]) # 23.5
print(dati["attivo"])      # True  ← true JSON diventa True Python

File JSON

Serializzare con dumps() restituisce una stringa normale: per salvarla su file è un attimo!

import json

letture = [
    {"id": 1, "temp": 22.1, "hum": 55},
    {"id": 2, "temp": 23.4, "hum": 58},
    {"id": 3, "temp": 21.8, "hum": 62},
]

file = open("letture.json", "w")
# scrive il file a capo con indentazione a 2 spazi: 
# rende il JSON facilmente leggibile anche dalle persone!
file.write( json.dumps(letture, indent=2) )
file.close()

Il file letture.json (scritto con indent=2) conterrà:

[
  {"id": 1, "temp": 22.1, "hum": 55},
  {"id": 2, "temp": 23.4, "hum": 58},
  {"id": 3, "temp": 21.8, "hum": 62}
]

Allo stesso modo, leggere un file JSON significa leggere una stringa con read() e poi deserializzarla con loads().

import json

file = open("letture.json", "r")
letture = json.loads( file.read() )
file.close()

print(type(letture))   # <class 'list'>
for r in letture:
    print(f"ID {r['id']}: {r['temp']}°C, {r['hum']}%")

Attenzione

Se il JSON è malformato, Python solleva json.JSONDecodeError:

Gestione degli errori su JSON
import json

testo_errato = '{"nome": "Andrea", "età": }'  # JSON non valido!

try:
    dati = json.loads(testo_errato)
except json.JSONDecodeError as e:
    print(f"Errore nel parsing JSON: {e}")

Esercizi su JSON


Esercizio 851

Crea un dizionario Python con almeno 5 chiavi (nome, cognome, età, città, hobby) e convertilo in una stringa JSON stampata in modo leggibile (indentata).


Esercizio 852

Parti dalla stringa JSON dell'Esercizio 1, convertila di nuovo in un dizionario Python e stampa solo i valori (non le chiavi) uno per riga.


Esercizio 853

Crea un dizionario Python che contenga: una stringa, un intero, un float, un booleano, None, una lista e un dizionario annidato. Convertilo in JSON e verifica come cambiano i tipi stampando il risultato con indent=2.


Esercizio 854

Crea un file config.json che contenga le impostazioni di un'applicazione: - nome app, versione, debug (bool), porte permesse (lista di interi).

Scrivi uno script che legge il file e stampa ogni impostazione con una riga descrittiva.


Esercizio 855

Crea una lista di almeno 4 dizionari, ognuno con i campi id, tipo, valore, unità. Salvala in sensori.json, poi rileggila e stampa solo i sensori con valore superiore a 25.


Esercizio 856

Leggi il file sensori.json creato nell'esercizio precedente, aggiungi un nuovo sensore alla lista e risalva il file. Verifica aprendo il file che il nuovo sensore sia presente.


Esercizio 857

Crea un dizionario che rappresenti una scuola: nome, indirizzo (a sua volta un dizionario con via, città, CAP), e una lista di classi (ogni classe ha nome e numero di studenti). Serializzalo in JSON e poi deserializzalo, stampando il nome della scuola e l'elenco delle classi con il numero di studenti.


Esercizio 858

Simula la ricezione di questo messaggio da un ESP32:

{
  "device_id": "ESP32-07",
  "timestamp": 1711450000,
  "readings": [
    {"sensor": "DHT11", "temp": 23.1, "hum": 61},
    {"sensor": "DS18B20", "temp": 23.4}
  ]
}

Scrivi uno script che: 1. Fa il parsing del messaggio. 2. Stampa device_id e timestamp. 3. Stampa temperatura e umidità di ogni sensore (se disponibili).


Esercizio 859

Crea uno script che simula un logger di dati. Ad ogni esecuzione: 1. Legge il file log.json (se esiste); se non esiste, parte da una lista vuota. 2. Aggiunge un nuovo record con {"lettura": N, "valore": <numero casuale tra 20 e 30>} dove N è il numero progressivo. 3. Salva il file aggiornato.

Esegui lo script più volte e verifica che il file cresca correttamente.