Skip to content

Lezione 9: Dati locali — JSON e persistenza

  • Capire cos’è JSON e come si usa in Python
  • Salvare dati su file con il modulo json
  • Caricare dati all’avvio dell’app
  • Creare una Todo List con persistenza

JSON (JavaScript Object Notation) è un formato per rappresentare dati strutturati. È il formato più usato per salvare e scambiare dati.

{
"nome": "Mario",
"eta": 17,
"studente": true,
"voti": [8, 7, 9],
"indirizzo": {
"via": "Roma",
"civico": 10
}
}

In Python, JSON corrisponde a:

{
"nome": "Mario",
"eta": 17,
"studente": True,
"voti": [8, 7, 9],
"indirizzo": {
"via": "Roma",
"civico": 10
}
}

import json
dati = {
"nome": "Mario",
"eta": 17,
"materie": ["Python", "Italiano", "Matematica"]
}
# Python → stringa JSON
json_string = json.dumps(dati, indent=2)
print(json_string)

Output:

{
"nome": "Mario",
"eta": 17,
"materie": ["Python", "Italiano", "Matematica"]
}

Convertire JSON → Python (deserializzare)

Section titled “Convertire JSON → Python (deserializzare)”
import json
json_string = '{"nome": "Mario", "eta": 17}'
dati = json.loads(json_string)
print(dati["nome"]) # Mario

In Flet, possiamo salvare i dati in un file JSON usando il modulo json standard di Python.

import json
import os
def salva_dati(filename, dati):
"""Salva una lista/dict in un file JSON."""
with open(filename, "w", encoding="utf-8") as f:
json.dump(dati, f, indent=2, ensure_ascii=False)
def carica_dati(filename):
"""Carica dati da un file JSON. Se non esiste, ritorna lista vuota."""
if os.path.exists(filename):
with open(filename, "r", encoding="utf-8") as f:
return json.load(f)
return []

Mettiamo tutto insieme: una Todo List che ricorda i task anche dopo aver chiuso l’app.

import flet as ft
import json
import os
FILE_DATI = "todo_data.json"
def main(page: ft.Page):
page.title = "Todo List Persistente"
page.padding = 30
page.scroll = ft.ScrollMode.AUTO
# Carica dati all'avvio
def carica_todo():
if os.path.exists(FILE_DATI):
with open(FILE_DATI, "r", encoding="utf-8") as f:
return json.load(f)
return []
# Salva dati su file
def salva_todo():
with open(FILE_DATI, "w", encoding="utf-8") as f:
json.dump(tasks, f, indent=2, ensure_ascii=False)
# Stato
tasks = carica_todo() # Lista di dizionari: {"testo": "...", "fatto": false}
# Widget lista
lista_tasks = ft.ListView(spacing=10, padding=10, expand=True)
def costruisci_lista():
"""Ricostruisce la UI della lista partendo da tasks."""
lista_tasks.controls.clear()
for i, task in enumerate(tasks):
# Checkbox per completato
checkbox = ft.Checkbox(
value=task["fatto"],
on_change=lambda e, idx=i: toggle_task(idx),
)
# Testo del task
testo = ft.Text(
task["testo"],
size=16,
style=ft.TextStyle(
decoration=ft.TextDecoration.LINE_THROUGH if task["fatto"] else None,
),
color="grey" if task["fatto"] else "black",
)
# Bottone elimina
elimina = ft.IconButton(
ft.icons.DELETE,
icon_color="red",
on_click=lambda e, idx=i: elimina_task(idx),
)
# Card
card = ft.Container(
content=ft.Row([checkbox, testo, elimina], alignment=ft.MainAxisAlignment.SPACE_BETWEEN),
padding=10,
bgcolor="white",
border_radius=8,
shadow=ft.BoxShadow(blur_radius=3, color=ft.colors.GREY_300),
)
lista_tasks.controls.append(card)
def aggiungi_task(e):
if input_task.value and input_task.value.strip():
tasks.append({"testo": input_task.value.strip(), "fatto": False})
salva_todo()
costruisci_lista()
input_task.value = ""
page.update()
def toggle_task(idx):
tasks[idx]["fatto"] = not tasks[idx]["fatto"]
salva_todo()
costruisci_lista()
page.update()
def elimina_task(idx):
tasks.pop(idx)
salva_todo()
costruisci_lista()
page.update()
def svuota_lista(e):
tasks.clear()
salva_todo()
costruisci_lista()
page.update()
# Input
input_task = ft.TextField(
hint_text="Nuovo task...",
expand=True,
on_submit=aggiungi_task,
)
# Costruisce interfaccia
page.add(
ft.Text("📋 Todo List", size=28, weight="bold"),
ft.Text(f"Task: {len(tasks)}", color="grey"),
ft.Divider(),
ft.Row([input_task, ft.ElevatedButton("", on_click=aggiungi_task)]),
ft.Row([
ft.OutlinedButton("🗑️ Svuota tutto", on_click=svuota_lista, icon=ft.icons.DELETE),
]),
ft.Divider(),
lista_tasks,
)
# Mostra dati caricati
costruisci_lista()
page.update()
ft.app(target=main)

def aggiungi_task(e):
# ... modifica tasks ...
salva_dati() # Salva OGNI volta che si modifica
bottone_salva = ft.ElevatedButton("Salva", on_click=lambda e: salva_dati())
bottone_salva = ft.ElevatedButton("Salva", on_click=lambda e: salva_con_feedback(e))
def salva_con_feedback(e):
salva_dati()
page.snack_bar = ft.SnackBar(ft.Text("✅ Dati salvati!"), open=True)
page.update()

A volte il file potrebbe essere danneggiato o non accessibile:

def carica_dati_sicura(filename):
"""Carica dati con gestione errori."""
try:
if os.path.exists(filename):
with open(filename, "r", encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
# File danneggiato → ricomincia da capo
print("File danneggiato, ricreo...")
return []

ComandoCosa fa
json.dumps(dati)Converte Python → stringa JSON
json.loads(stringa)Converte stringa JSON → Python
json.dump(dati, file)Salva dati su file
json.load(file)Carica dati da file
os.path.exists(file)Controlla se un file esiste
open(file, "w")Apre file in scrittura
open(file, "r")Apre file in lettura

Crea un file diario.py che realizzi un diario personale con persistenza:

  1. All’avvio, carica i post salvati dal file diario.json
  2. Mostra la lista dei post (ogni post ha: titolo, data, testo)
  3. Un form per aggiungere un nuovo post con:
    • Titolo (TextField)
    • Testo (TextField multiline)
    • Data (messa automaticamente: import datetime; oggi = str(datetime.date.today()))
  4. Bottone “Aggiungi” che salva il post e aggiorna la lista
  5. Ogni post nella lista mostra: titolo, data, prime parole del testo
  6. Bottone “Elimina” su ogni post per rimuoverlo
  7. Tutto deve essere persistente: alla prossima apertura, i post devono essere ancora lì
[
{
"titolo": "Primo giorno di scuola",
"data": "2026-09-15",
"testo": "Oggi è iniziato il nuovo anno scolastico..."
},
...
]
  • Usa datetime.date.today().isoformat() per la data automatica
  • Per mostrare solo l’inizio del testo: post["testo"][:50] + "..."
  • Usa ft.Card per ogni post
  • Per il testo lungo, usa multiline=True nel TextField