⭐ Powered by Prof. Carello

📘 Guida Completa JSON

Tutorial Avanzato, Parsing Python ed Esercizi Pratici

👨‍🏫 A cura del Prof. Nicolò Carello
Docente di Informatica e Sistemi di Rete

1. Introduzione a JSON (JavaScript Object Notation)

JSON (JavaScript Object Notation) è uno dei formati di scambio dati più utilizzati al mondo. Nato come sottoinsieme della sintassi JavaScript, è diventato uno standard indipendente dal linguaggio di programmazione, supportato praticamente ovunque.

🎯 Perché JSON è così importante?

JSON rappresenta la lingua franca della comunicazione tra sistemi informatici moderni. Quando un'applicazione web comunica con un server, quando un'app mobile recupera dati da un'API, o quando microservizi diversi si scambiano informazioni, molto probabilmente stanno utilizzando JSON.

La sua semplicità e leggibilità lo rendono ideale sia per gli esseri umani che per le macchine, un equilibrio raro nel mondo dell'informatica.

Storia e Evoluzione di JSON

JSON è stato specificato per la prima volta da Douglas Crockford nei primi anni 2000. Crockford cercava un formato che fosse:

  • Leggibile dall'uomo: a differenza di formati binari, JSON può essere letto e compreso facilmente
  • Semplice da parsare: la sintassi minimale rende il parsing veloce ed efficiente
  • Indipendente dal linguaggio: non legato a JavaScript nonostante il nome
  • Basato su strutture dati universali: oggetti (mappe chiave-valore) e array esistono in quasi tutti i linguaggi

Nel 2013, JSON è diventato uno standard ufficiale dell'IETF (Internet Engineering Task Force) con la RFC 7159, successivamente aggiornata alla RFC 8259 nel 2017.

Caratteristiche Principali

💻 Esempio: Struttura JSON Completa
JSON
{
  "studente": {
    "nome": "Mario",
    "cognome": "Rossi",
    "eta": 20,
    "matricola": "12345",
    "iscritto": true,
    "corso": "Informatica",
    "media_voti": 27.5,
    "esami_sostenuti": [
      {
        "materia": "Programmazione",
        "voto": 30,
        "lode": true,
        "data": "2024-01-15"
      },
      {
        "materia": "Basi di Dati",
        "voto": 28,
        "lode": false,
        "data": "2024-02-10"
      },
      {
        "materia": "Reti di Calcolatori",
        "voto": 25,
        "lode": false,
        "data": "2024-03-05"
      }
    ],
    "contatti": {
      "email": "mario.rossi@university.it",
      "telefono": "+39 123 456 7890",
      "indirizzo": {
        "via": "Via Roma",
        "numero": 10,
        "citta": "Milano",
        "cap": "20100"
      }
    },
    "note": null
  }
}

Questo esempio mostra come JSON permetta di rappresentare strutture dati complesse in modo gerarchico e facilmente comprensibile. Notate come si possano annidare oggetti (come "contatti" e "indirizzo") e array (come "esami_sostenuti").

Vantaggi di JSON

Vantaggio Descrizione Applicazione Pratica
Leggibilità Formato testuale facilmente comprensibile Debug rapido, documentazione chiara
Leggerezza Sintassi minimale, poco overhead Trasferimento dati veloce su rete
Interoperabilità Supporto universale in tutti i linguaggi Integrazione tra sistemi eterogenei
Validazione Schema JSON per validare struttura Contratti API, quality assurance
Parsing Efficiente Parser nativi in tutti i linguaggi Performance elevate nelle applicazioni

JSON vs Altri Formati

⚖️ Confronto con XML

XML (eXtensible Markup Language) era lo standard de facto prima di JSON. Ecco le differenze chiave:

  • Verbosità: XML richiede tag di apertura e chiusura, JSON è più conciso
  • Tipo di dati: JSON supporta nativamente numeri, booleani, null; XML tratta tutto come stringa
  • Array: JSON ha array nativi; in XML servono convenzioni
  • Dimensioni: file JSON tipicamente 30-40% più piccoli di equivalenti XML

Tuttavia, XML rimane preferibile quando servono: metadati complessi, namespace, validazione schema robusta (XSD), o trasformazioni (XSLT).

📊 Quando usare JSON?

  • API REST: standard de facto per servizi web RESTful
  • Configurazioni: file di configurazione applicazioni (package.json, tsconfig.json)
  • Database NoSQL: MongoDB, CouchDB usano JSON/BSON per documenti
  • Messaging: scambio messaggi tra microservizi
  • Storage dati: salvataggio stato applicazioni, cache, sessioni
  • Logging: log strutturati per analisi automatica

2. Sintassi e Struttura JSON

La sintassi JSON è estremamente semplice ma rigorosa. Ogni carattere ha un significato preciso e la mancanza anche di una sola virgola o parentesi può rendere il JSON invalido. Comprendere a fondo queste regole è fondamentale per lavorare efficacemente con JSON.

Regole Fondamentali della Sintassi

📐 Le 7 Regole d'Oro di JSON

  1. Coppie chiave-valore: i dati sono organizzati in coppie "chiave": valore
  2. Chiavi sempre tra virgolette doppie: "nome" è valido, 'nome' o nome non lo sono
  3. Virgole per separare: elementi in array e proprietà in oggetti
  4. NO virgola finale: l'ultimo elemento non deve avere virgola dopo
  5. Graffe per oggetti: { }
  6. Quadre per array: [ ]
  7. Sensibile a maiuscole/minuscole: trueTrue

Strutture Dati: Oggetti

Un oggetto JSON è una collezione non ordinata di coppie chiave-valore, racchiusa tra graffe { }. È l'equivalente di un dizionario in Python, una mappa in Java, o un oggetto in JavaScript.

🔧 Oggetti JSON: Esempi Pratici
JSON
// Oggetto semplice
{
  "nome": "Giovanni",
  "eta": 25
}

// Oggetto con tipi diversi
{
  "prodotto": "Laptop",
  "prezzo": 899.99,
  "disponibile": true,
  "quantita": 15,
  "descrizione": "Laptop ad alte prestazioni",
  "note": null
}

// Oggetti annidati (nested objects)
{
  "azienda": {
    "nome": "TechCorp",
    "fondazione": 2010,
    "sede": {
      "citta": "Milano",
      "indirizzo": "Via Innovazione 42",
      "coordinate": {
        "latitudine": 45.4642,
        "longitudine": 9.1900
      }
    },
    "dipendenti": 250
  }
}

// Oggetto con array
{
  "biblioteca": {
    "nome": "Biblioteca Comunale",
    "libri": [
      {
        "titolo": "Il Nome della Rosa",
        "autore": "Umberto Eco",
        "isbn": "978-8845292620",
        "anno": 1980
      },
      {
        "titolo": "Se questo è un uomo",
        "autore": "Primo Levi",
        "isbn": "978-8806211875",
        "anno": 1947
      }
    ]
  }
}

Strutture Dati: Array

Un array JSON è una lista ordinata di valori, racchiusa tra parentesi quadre [ ]. Gli elementi possono essere di qualsiasi tipo JSON, anche misti.

📚 Array JSON: Esempi Completi
JSON
// Array di numeri
[1, 2, 3, 4, 5]

// Array di stringhe
["rosso", "verde", "blu", "giallo"]

// Array di oggetti (molto comune nelle API)
[
  {
    "id": 1,
    "nome": "Alice",
    "ruolo": "Developer"
  },
  {
    "id": 2,
    "nome": "Bob",
    "ruolo": "Designer"
  },
  {
    "id": 3,
    "nome": "Charlie",
    "ruolo": "Manager"
  }
]

// Array misto (possibile ma sconsigliato)
[
  42,
  "testo",
  true,
  null,
  {"chiave": "valore"},
  [1, 2, 3]
]

// Array multidimensionale (matrice)
[
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
]

// Caso d'uso reale: lista transazioni
{
  "transazioni": [
    {
      "id": "TRX001",
      "data": "2024-11-05",
      "importo": 150.50,
      "tipo": "entrata",
      "descrizione": "Vendita prodotto A"
    },
    {
      "id": "TRX002",
      "data": "2024-11-04",
      "importo": 75.00,
      "tipo": "uscita",
      "descrizione": "Acquisto materiali"
    }
  ]
}

Formattazione e Best Practices

⚠️ Errori Comuni di Sintassi

Questi sono gli errori più frequenti che causano JSON invalido:

Errori da Evitare
ERRORI
// ❌ ERRORE: virgola finale
{
  "nome": "Mario",
  "eta": 30,  // <- virgola di troppo
}

// ❌ ERRORE: virgolette singole
{
  'nome': 'Mario'  // deve essere "nome": "Mario"
}

// ❌ ERRORE: chiavi senza virgolette
{
  nome: "Mario"  // deve essere "nome": "Mario"
}

// ❌ ERRORE: commenti (JSON non li supporta!)
{
  "nome": "Mario",  // questo è Mario
  "eta": 30
}

// ❌ ERRORE: valori undefined
{
  "nome": "Mario",
  "cognome": undefined  // deve essere null
}

// ❌ ERRORE: funzioni (non supportate)
{
  "calcola": function() { return 42; }
}

// ✅ CORRETTO
{
  "nome": "Mario",
  "eta": 30,
  "cognome": null,
  "attivo": true
}

✅ Best Practices per Scrivere JSON

  • Indentazione consistente: usa 2 o 4 spazi (mai tab)
  • Nomi chiavi descrittivi: "data_nascita" meglio di "d"
  • Snake_case o camelCase: scegli uno stile e mantienilo ("first_name" o "firstName")
  • Evita array misti: meglio array omogenei (tutti oggetti o tutti numeri)
  • Usa null esplicitamente: meglio "campo": null che omettere il campo
  • Documenta la struttura: usa JSON Schema per API pubbliche

3. Tipi di Dati JSON

JSON supporta sei tipi di dati fondamentali. La comprensione profonda di questi tipi è essenziale perché influenza come i dati vengono interpretati e manipolati dai programmi.

1. Stringhe (String)

Le stringhe sono sequenze di caratteri Unicode racchiuse tra virgolette doppie. JSON supporta l'encoding UTF-8, permettendo l'uso di qualsiasi carattere Unicode.

📝 Stringhe JSON: Caratteri Speciali ed Escape
JSON
{
  // Stringhe semplici
  "testo_semplice": "Ciao mondo!",
  "percorso": "/usr/local/bin",
  
  // Stringhe con caratteri speciali escape
  "con_virgolette": "Ha detto: \"Ciao!\"",
  "backslash": "C:\\Users\\Documents",
  "nuova_riga": "Prima riga\nSeconda riga",
  "tab": "Nome\tCognome",
  "backspace": "Testo\bCancellato",
  "form_feed": "Pagina\fNuova",
  "carriage_return": "Ritorno\rCarrello",
  
  // Unicode
  "unicode": "Stella: \u2605",
  "emoji": "Cuore: \u2764\uFE0F",
  "caratteri_speciali": "àèéìòù çñ ßø",
  
  // Stringhe multilingua
  "italiano": "Buongiorno",
  "inglese": "Good morning",
  "giapponese": "おはよう",
  "arabo": "صباح الخير",
  "russo": "Доброе утро",
  
  // Stringa vuota (valida)
  "stringa_vuota": "",
  
  // Stringhe lunghe
  "descrizione_lunga": "Questo è un esempio di stringa molto lunga che può contenere molte informazioni dettagliate su un argomento specifico senza limiti di lunghezza."
}

🔤 Caratteri di Escape in JSON

Sequenza Significato Esempio
\" Virgolette doppie "Disse: \"Hi\""
\\ Backslash "C:\\path"
\/ Forward slash (opzionale) "http:\/\/site"
\n Nuova riga (newline) "Riga1\nRiga2"
\t Tabulazione "Col1\tCol2"
\uXXXX Carattere Unicode "\u00A9" → ©

2. Numeri (Number)

JSON rappresenta i numeri in formato decimale (base 10). Non c'è distinzione tra interi e decimali nella specifica JSON, anche se i parser possono gestirli diversamente.

🔢 Numeri JSON: Tutti i Formati
JSON
{
  // Interi
  "intero_positivo": 42,
  "intero_negativo": -17,
  "zero": 0,
  
  // Decimali (floating point)
  "decimale": 3.14159,
  "prezzo": 19.99,
  "percentuale": 0.75,
  
  // Notazione scientifica
  "molto_grande": 1.23e10,      // 12,300,000,000
  "molto_piccolo": 1.23e-10,    // 0.000000000123
  "equivalente": 6.022e23,      // Numero di Avogadro
  
  // Negativi con decimale
  "temperatura": -273.15,
  "debito": -1000.50,
  
  // Casi limite
  "numero_grande": 9007199254740991,  // MAX_SAFE_INTEGER in JavaScript
  
  // ❌ NON VALIDI in JSON
  // "hex": 0xFF,              // no esadecimale
  // "ottale": 0o77,           // no ottale
  // "binario": 0b1010,        // no binario
  // "infinito": Infinity,     // no infinito
  // "not_a_number": NaN,      // no NaN
  // "leading_zero": 007       // no zeri iniziali
}

⚠️ Limiti e Precisione dei Numeri

Attenzione alla precisione: JSON non specifica limiti per i numeri, ma i linguaggi di programmazione sì. JavaScript, ad esempio, usa floating-point a 64 bit (IEEE 754), che può causare problemi di precisione:

  • Interi sicuri solo fino a ±9007199254740991
  • Decimali possono avere errori di arrotondamento (es: 0.1 + 0.2 ≠ 0.3)
  • Per valori monetari, meglio usare stringhe o interi (centesimi)

3. Booleani (Boolean)

I valori booleani rappresentano vero o falso. JSON usa true e false (tutto minuscolo, senza virgolette).

Booleani JSON
JSON
{
  "attivo": true,
  "verificato": false,
  "premium": true,
  "accetta_newsletter": false,
  
  // Uso in condizioni
  "utente": {
    "nome": "Marco",
    "admin": false,
    "logged_in": true,
    "email_verified": true
  },
  
  // ❌ NON VALIDI
  // "errore1": True,     // deve essere tutto minuscolo
  // "errore2": "true",   // non deve essere stringa
  // "errore3": 1,        // non usare 1/0 per bool
  // "errore4": yes       // non esistono yes/no
}

4. Null

null rappresenta l'assenza intenzionale di valore. È diverso da stringa vuota, zero, o false.

Null in JSON: Quando e Perché
JSON
{
  // Null per campi opzionali non valorizzati
  "secondo_nome": null,
  "data_fine_contratto": null,
  "foto_profilo": null,
  
  // Differenza tra null e altri valori
  "esempi": {
    "valore_null": null,           // assenza di valore
    "stringa_vuota": "",           // stringa presente ma vuota
    "numero_zero": 0,              // numero valido
    "false_booleano": false,       // boolean esplicito
    "array_vuoto": [],             // array valido senza elementi
    "oggetto_vuoto": {}            // oggetto valido senza proprietà
  },
  
  // Uso semantico di null
  "utente_eliminato": {
    "id": 123,
    "nome": "Account Eliminato",
    "email": null,                 // email rimossa
    "ultimo_accesso": null,        // mai più accessibile
    "note": null
  }
}

🎯 Null vs Undefined vs Campo Mancante

Differenze importanti:

  • "campo": null → campo presente con valore null (assenza intenzionale)
  • Campo non presente → omesso completamente dall'oggetto
  • undefined → non esiste in JSON! È un concetto JavaScript

Best practice: usa null quando vuoi indicare "nessun valore" ma mantieni il campo nella struttura.

5. Oggetti (Object)

Già discussi nella sezione sintassi. Collezioni non ordinate di coppie chiave-valore.

6. Array (Array)

Già discussi nella sezione sintassi. Liste ordinate di valori.

Tabella Riepilogativa Tipi

Tipo Descrizione Esempio JSON Tipo Python
String Testo tra "" "Ciao" str
Number Intero o decimale 42, 3.14, -17, 1e10 int, float
Boolean true o false true, false bool
Null Assenza di valore null None
Object Collezione {chiave: valore} {"a": 1} dict
Array Lista ordinata [valori] [1, 2, 3] list

4. Validazione e Gestione Errori JSON

La validazione JSON è cruciale per garantire che i dati rispettino la struttura attesa. Un JSON sintatticamente corretto potrebbe comunque contenere dati semanticamente sbagliati per la tua applicazione.

Errori Sintattici Comuni

❌ Top 10 Errori JSON

Questi sono gli errori più frequenti che causano il fallimento del parsing JSON:

🐛 Errori JSON Comuni e Come Risolverli
ERRORI
// 1. Virgola finale (trailing comma)
{
  "nome": "Mario",
  "eta": 30,  // ❌ virgola di troppo
}

// 2. Virgolette singole invece di doppie
{
  'nome': 'Mario'  // ❌ usa "
}

// 3. Chiavi senza virgolette
{
  nome: "Mario"  // ❌ la chiave deve essere "nome"
}

// 4. Commenti (non supportati in JSON standard)
{
  "nome": "Mario",  // ❌ questo commento causa errore
  "eta": 30
}

// 5. Virgola mancante tra elementi
{
  "nome": "Mario"   // ❌ manca virgola
  "eta": 30
}

// 6. Valori booleani in maiuscolo
{
  "attivo": True  // ❌ deve essere true
}

// 7. Numeri con zeri iniziali
{
  "codice": 007  // ❌ deve essere 7 o "007"
}

// 8. Virgolette non chiuse
{
  "nome": "Mario  // ❌ manca "
}

// 9. Escape scorretto
{
  "path": "C:\Users\Mario"  // ❌ deve essere "C:\\Users\\Mario"
}

// 10. Caratteri speciali non escaped
{
  "citazione": "Lui disse: "Ciao""  // ❌ deve essere \"Ciao\"
}

Validazione con JSON Schema

JSON Schema è uno standard per descrivere la struttura, i tipi e le regole di validazione di documenti JSON. È come un "contratto" che il JSON deve rispettare.

📋 JSON Schema: Esempio Completo
JSON Schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Studente",
  "description": "Schema per validare i dati di uno studente",
  "type": "object",
  "properties": {
    "nome": {
      "type": "string",
      "minLength": 2,
      "maxLength": 50,
      "pattern": "^[A-Za-zÀ-ÿ ]+$",
      "description": "Nome dello studente (solo lettere)"
    },
    "cognome": {
      "type": "string",
      "minLength": 2,
      "maxLength": 50
    },
    "eta": {
      "type": "integer",
      "minimum": 16,
      "maximum": 99,
      "description": "Età dello studente"
    },
    "email": {
      "type": "string",
      "format": "email",
      "description": "Email istituzionale"
    },
    "matricola": {
      "type": "string",
      "pattern": "^[0-9]{6}$",
      "description": "Matricola a 6 cifre"
    },
    "corso": {
      "type": "string",
      "enum": ["Informatica", "Ingegneria", "Matematica", "Fisica"],
      "description": "Corso di laurea"
    },
    "media_voti": {
      "type": "number",
      "minimum": 18,
      "maximum": 30,
      "description": "Media ponderata"
    },
    "esami_sostenuti": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "materia": {"type": "string"},
          "voto": {
            "type": "integer",
            "minimum": 18,
            "maximum": 30
          },
          "lode": {"type": "boolean"},
          "data": {
            "type": "string",
            "format": "date"
          }
        },
        "required": ["materia", "voto", "data"]
      },
      "minItems": 0,
      "maxItems": 50
    }
  },
  "required": ["nome", "cognome", "matricola", "email"],
  "additionalProperties": false
}

📖 Elementi Principali di JSON Schema

  • type: specifica il tipo di dato (string, number, integer, boolean, object, array, null)
  • properties: definisce le proprietà di un oggetto
  • required: array con nomi delle proprietà obbligatorie
  • minimum/maximum: range per numeri
  • minLength/maxLength: lunghezza stringhe
  • pattern: regex per validare stringhe
  • enum: lista valori ammessi
  • format: formati predefiniti (email, date, uri, ipv4, ecc.)
  • items: schema per elementi di un array
  • additionalProperties: se false, non permette proprietà extra

5. JSON in Python: Il Modulo json

Python offre il modulo json nella libreria standard per lavorare con JSON. È potente, semplice e perfettamente integrato con le strutture dati native di Python.

Serializzazione: Da Python a JSON (dumps/dump)

La serializzazione (encoding) è il processo di conversione di oggetti Python in stringa JSON.

🐍 json.dumps() - Serializzazione Base
Python
import json

# Dizionario Python
studente = {
    "nome": "Alice",
    "cognome": "Bianchi",
    "eta": 22,
    "matricola": "123456",
    "attivo": True,
    "voti": [28, 30, 27, 29],
    "indirizzo": {
        "via": "Via Roma",
        "citta": "Milano"
    },
    "note": None
}

# Conversione a stringa JSON
json_string = json.dumps(studente)
print(json_string)
# Output: {"nome": "Alice", "cognome": "Bianchi", ...}

# Con indentazione per leggibilità
json_formattato = json.dumps(studente, indent=4)
print(json_formattato)
# Output:
# {
#     "nome": "Alice",
#     "cognome": "Bianchi",
#     ...
# }

# Con ordinamento chiavi alfabetico
json_ordinato = json.dumps(studente, indent=4, sort_keys=True)

# Rimuovere spazi per compattezza
json_compatto = json.dumps(studente, separators=(',', ':'))
print(json_compatto)
# Output: {"nome":"Alice","cognome":"Bianchi",...}

Deserializzazione: Da JSON a Python (loads/load)

La deserializzazione (decoding) converte una stringa JSON in oggetti Python.

🐍 json.loads() - Deserializzazione e Parsing
Python
import json

# Stringa JSON
json_data = '''
{
    "nome": "Bob",
    "eta": 25,
    "skills": ["Python", "JavaScript", "SQL"],
    "esperienza": {
        "anni": 3,
        "aziende": ["TechCorp", "StartupX"]
    },
    "certificazioni": null
}
'''

# Parsing JSON -> Python dict
persona = json.loads(json_data)

# Accesso ai dati
print(persona["nome"])              # "Bob"
print(persona["eta"])               # 25
print(persona["skills"][0])         # "Python"
print(persona["esperienza"]["anni"]) # 3

# Verifica tipo
print(type(persona))                # 
print(type(persona["skills"]))      # 
print(type(persona["certificazioni"])) # 

# Iterazione
for skill in persona["skills"]:
    print(f"- {skill}")

# Gestione errori
try:
    dati_invalidi = '{"nome": "Test"'  # JSON malformato
    json.loads(dati_invalidi)
except json.JSONDecodeError as e:
    print(f"Errore parsing JSON: {e}")
    print(f"Posizione errore: linea {e.lineno}, colonna {e.colno}")

Tabella di Conversione Tipi

Python → JSON (dumps) JSON → Python (loads)
dict → object object → dict
list, tuple → array array → list
str → string string → str
int, float → number number → int o float
True → true true → True
False → false false → False
None → null null → None

⚠️ Tipi Python NON Serializzabili

Alcuni tipi Python non hanno equivalenti diretti in JSON e causeranno errore:

  • set → usa list(set) oppure custom encoder
  • datetime → converti in stringa ISO 8601
  • bytes → usa base64 encoding
  • Decimal → converti in float o stringa
  • complex → rappresenta come dict {"real": x, "imag": y}
  • function/class → non serializzabili

Encoder e Decoder Personalizzati

🔧 Custom Encoder per Tipi Complessi
Python
import json
from datetime import datetime, date
from decimal import Decimal

class CustomEncoder(json.JSONEncoder):
    """Encoder personalizzato per tipi Python non standard"""
    
    def default(self, obj):
        # Gestione datetime
        if isinstance(obj, datetime):
            return {
                "_type": "datetime",
                "value": obj.isoformat()
            }
        
        # Gestione date
        if isinstance(obj, date):
            return {
                "_type": "date",
                "value": obj.isoformat()
            }
        
        # Gestione Decimal
        if isinstance(obj, Decimal):
            return {
                "_type": "decimal",
                "value": str(obj)
            }
        
        # Gestione set
        if isinstance(obj, set):
            return {
                "_type": "set",
                "value": list(obj)
            }
        
        # Gestione bytes
        if isinstance(obj, bytes):
            import base64
            return {
                "_type": "bytes",
                "value": base64.b64encode(obj).decode('utf-8')
            }
        
        # Se non gestito, usa comportamento default
        return super().default(obj)

def custom_decoder(dct):
    """Decoder personalizzato per ricostruire oggetti Python"""
    
    if "_type" not in dct:
        return dct
    
    tipo = dct["_type"]
    valore = dct["value"]
    
    if tipo == "datetime":
        return datetime.fromisoformat(valore)
    elif tipo == "date":
        return date.fromisoformat(valore)
    elif tipo == "decimal":
        return Decimal(valore)
    elif tipo == "set":
        return set(valore)
    elif tipo == "bytes":
        import base64
        return base64.b64decode(valore)
    
    return dct

# Esempio utilizzo
dati = {
    "evento": "Conferenza",
    "data": datetime.now(),
    "partecipanti": {"Alice", "Bob", "Charlie"},
    "budget": Decimal("1500.50"),
    "immagine": b"dati binari esempio"
}

# Serializzazione con encoder custom
json_string = json.dumps(dati, cls=CustomEncoder, indent=2)
print(json_string)

# Deserializzazione con decoder custom
dati_ripristinati = json.loads(json_string, object_hook=custom_decoder)
print(type(dati_ripristinati["data"]))  # 
print(type(dati_ripristinati["partecipanti"]))  # 

6. Lavorare con File JSON

Nella pratica, JSON viene spesso letto da file o salvato su file. Python rende queste operazioni estremamente semplici con le funzioni json.load() e json.dump().

Lettura da File: json.load()

📂 Leggere File JSON in Python
Python
import json
from pathlib import Path

# Metodo 1: Lettura base
with open('studenti.json', 'r', encoding='utf-8') as file:
    studenti = json.load(file)
    print(studenti)

# Metodo 2: Con gestione errori completa
def leggi_json(percorso_file):
    """Legge file JSON con gestione errori robusta"""
    try:
        # Verifica esistenza file
        if not Path(percorso_file).exists():
            print(f"File {percorso_file} non trovato")
            return None
        
        # Lettura file
        with open(percorso_file, 'r', encoding='utf-8') as f:
            dati = json.load(f)
            return dati
            
    except json.JSONDecodeError as e:
        print(f"Errore parsing JSON: {e}")
        print(f"Riga {e.lineno}, colonna {e.colno}")
        return None
    except PermissionError:
        print(f"Permessi insufficienti per leggere {percorso_file}")
        return None
    except Exception as e:
        print(f"Errore inaspettato: {e}")
        return None

# Utilizzo
dati = leggi_json('configurazione.json')
if dati:
    print(f"Configurazione caricata con {len(dati)} elementi")

# Metodo 3: Lettura con valori predefiniti
def leggi_con_default(percorso, default=None):
    """Legge JSON, ritorna default se errore"""
    try:
        with open(percorso, 'r', encoding='utf-8') as f:
            return json.load(f)
    except:
        return default if default is not None else {}

config = leggi_con_default('config.json', {"debug": False, "timeout": 30})

Scrittura su File: json.dump()

💾 Salvare Dati in File JSON
Python
import json
from pathlib import Path

# Dati da salvare
database = {
    "studenti": [
        {"id": 1, "nome": "Alice", "voto": 28},
        {"id": 2, "nome": "Bob", "voto": 30},
        {"id": 3, "nome": "Charlie", "voto": 25}
    ],
    "corso": "Python Avanzato",
    "anno": 2024
}

# Metodo 1: Scrittura base
with open('database.json', 'w', encoding='utf-8') as file:
    json.dump(database, file, indent=4, ensure_ascii=False)

# Metodo 2: Funzione salvataggio sicuro
def salva_json(dati, percorso_file, indent=4):
    """Salva dati in JSON con backup e verifica"""
    percorso = Path(percorso_file)
    
    # Crea backup se file esiste
    if percorso.exists():
        backup = percorso.with_suffix('.json.backup')
        percorso.rename(backup)
        print(f"Backup creato: {backup}")
    
    try:
        # Scrittura file temporaneo
        temp = percorso.with_suffix('.tmp')
        with open(temp, 'w', encoding='utf-8') as f:
            json.dump(dati, f, indent=indent, ensure_ascii=False, sort_keys=True)
        
        # Verifica validità
        with open(temp, 'r') as f:
            json.load(f)  # Test parsing
        
        # Rinomina temporaneo in definitivo
        temp.rename(percorso)
        print(f"File salvato: {percorso}")
        
        # Rimuovi backup se tutto ok
        if percorso.with_suffix('.json.backup').exists():
            percorso.with_suffix('.json.backup').unlink()
        
        return True
        
    except Exception as e:
        print(f"Errore salvataggio: {e}")
        # Ripristina backup
        if percorso.with_suffix('.json.backup').exists():
            percorso.with_suffix('.json.backup').rename(percorso)
        return False

# Utilizzo
successo = salva_json(database, 'archivio_studenti.json')

# Metodo 3: Append a file JSON (array)
def aggiungi_a_json_array(nuovo_elemento, percorso_file):
    """Aggiunge elemento a array JSON esistente"""
    # Leggi dati esistenti
    if Path(percorso_file).exists():
        with open(percorso_file, 'r') as f:
            dati = json.load(f)
    else:
        dati = []
    
    # Aggiungi nuovo elemento
    dati.append(nuovo_elemento)
    
    # Salva
    with open(percorso_file, 'w', encoding='utf-8') as f:
        json.dump(dati, f, indent=4)

# Esempio
aggiungi_a_json_array(
    {"id": 4, "nome": "Diana", "voto": 29},
    'studenti_lista.json'
)

💡 Best Practices per File JSON

  • encoding='utf-8': sempre specificare per caratteri speciali
  • ensure_ascii=False: mantiene caratteri UTF-8 leggibili
  • indent: usa 2 o 4 spazi per leggibilità
  • Gestione errori: usa try-except per file e parsing
  • Backup: crea backup prima di sovrascrivere file importanti
  • Validazione: verifica JSON dopo scrittura
  • Atomicità: usa file temporanei e rename per scritture sicure

7. Tecniche Avanzate con JSON

Streaming JSON per File Grandi

Per file JSON molto grandi, caricarli interamente in memoria può causare problemi. Lo streaming permette di processare JSON incrementalmente.

🌊 Streaming JSON con ijson
Python
import ijson  # pip install ijson

# Esempio: File JSON enorme con array di oggetti
# {
#   "utenti": [
#     {"id": 1, "nome": "Alice", ...},
#     {"id": 2, "nome": "Bob", ...},
#     ...milioni di elementi...
#   ]
# }

def processa_json_streaming(percorso_file):
    """Processa file JSON enorme senza caricarlo tutto in memoria"""
    
    with open(percorso_file, 'rb') as file:
        # Parser incrementale
        parser = ijson.items(file, 'utenti.item')
        
        count = 0
        for utente in parser:
            # Processa ogni utente uno alla volta
            if utente.get('eta', 0) > 25:
                print(f"Utente: {utente['nome']}")
                count += 1
            
            # Limita output per esempio
            if count >= 10:
                break

# Lettura selettiva di campi specifici
def estrai_campo_specifico(percorso_file, campo):
    """Estrae solo un campo specifico dal JSON"""
    
    with open(percorso_file, 'rb') as file:
        # Cerca pattern specifico
        parser = ijson.items(file, f'dati.{campo}')
        
        for valore in parser:
            yield valore  # Generator per efficienza memoria

# Esempio
for valore in estrai_campo_specifico('big_data.json', 'prezzi.item'):
    print(valore)

Librerie Alternative per Performance

🚀 Librerie JSON ad Alte Prestazioni

Per applicazioni che necessitano di performance estreme:

orjson e ujson - Performance Boost
Python
import json
import orjson  # pip install orjson
import ujson   # pip install ujson
import time

# Dati di test
dati_grandi = {
    "utenti": [
        {"id": i, "nome": f"Utente{i}", "voti": list(range(20))}
        for i in range(10000)
    ]
}

# Benchmark serializzazione
def benchmark_serializzazione():
    # json standard
    start = time.time()
    _ = json.dumps(dati_grandi)
    tempo_json = time.time() - start
    
    # orjson (il più veloce)
    start = time.time()
    _ = orjson.dumps(dati_grandi)
    tempo_orjson = time.time() - start
    
    # ujson
    start = time.time()
    _ = ujson.dumps(dati_grandi)
    tempo_ujson = time.time() - start
    
    print(f"json:   {tempo_json:.4f}s")
    print(f"orjson: {tempo_orjson:.4f}s (speedup: {tempo_json/tempo_orjson:.2f}x)")
    print(f"ujson:  {tempo_ujson:.4f}s (speedup: {tempo_json/tempo_ujson:.2f}x)")

# orjson - Uso avanzato
import orjson

# Serializzazione con opzioni
json_bytes = orjson.dumps(
    dati_grandi,
    option=orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS
)

# orjson restituisce bytes, non str
json_str = json_bytes.decode('utf-8')

# Deserializzazione ultrarapida
dati = orjson.loads(json_bytes)

JSON e API REST

JSON è lo standard de facto per le API REST. Vediamo pattern comuni.

🌐 Consumare API REST con requests
Python
import requests
import json

# GET request - Ottenere dati
response = requests.get('https://api.example.com/users/123')
if response.status_code == 200:
    utente = response.json()  # Automatic JSON decoding
    print(utente['nome'])

# POST request - Inviare dati JSON
nuovo_utente = {
    "nome": "Mario",
    "email": "mario@example.com",
    "ruolo": "admin"
}

response = requests.post(
    'https://api.example.com/users',
    json=nuovo_utente,  # Automatic JSON encoding
    headers={'Authorization': 'Bearer TOKEN'}
)

if response.status_code == 201:
    print("Utente creato:", response.json()['id'])

# PUT request - Aggiornare dati
aggiornamento = {"ruolo": "super_admin"}
response = requests.put(
    'https://api.example.com/users/123',
    json=aggiornamento
)

# DELETE request
response = requests.delete('https://api.example.com/users/123')

# Gestione errori completa
def chiamata_api_sicura(url, metodo='GET', dati=None):
    """Wrapper per chiamate API con gestione errori"""
    try:
        if metodo == 'GET':
            response = requests.get(url, timeout=10)
        elif metodo == 'POST':
            response = requests.post(url, json=dati, timeout=10)
        
        response.raise_for_status()  # Solleva eccezione per status 4xx/5xx
        return response.json()
        
    except requests.exceptions.ConnectionError:
        print("Errore connessione")
    except requests.exceptions.Timeout:
        print("Timeout richiesta")
    except requests.exceptions.HTTPError as e:
        print(f"Errore HTTP: {e.response.status_code}")
    except json.JSONDecodeError:
        print("Risposta non è JSON valido")
    
    return None

8. Esercizi Pratici

Mettiamo in pratica quanto appreso con esercizi di difficoltà crescente. Ogni esercizio include la soluzione completa.

1 Gestione Rubrica Telefonica JSON

Obiettivo: Creare un programma Python per gestire una rubrica telefonica salvata in JSON.

Requisiti:

  • Aggiungere contatto (nome, telefono, email)
  • Cercare contatto per nome
  • Eliminare contatto
  • Visualizzare tutti i contatti
  • Salvare automaticamente su file JSON

💡 Soluzione Completa

Rubrica Telefonica in Python
Python
import json
from pathlib import Path

class Rubrica:
    def __init__(self, percorso_file='rubrica.json'):
        self.percorso_file = percorso_file
        self.contatti = self.carica_rubrica()
    
    def carica_rubrica(self):
        """Carica rubrica da file JSON"""
        if Path(self.percorso_file).exists():
            with open(self.percorso_file, 'r', encoding='utf-8') as f:
                return json.load(f)
        return []
    
    def salva_rubrica(self):
        """Salva rubrica su file JSON"""
        with open(self.percorso_file, 'w', encoding='utf-8') as f:
            json.dump(self.contatti, f, indent=4, ensure_ascii=False)
    
    def aggiungi_contatto(self, nome, telefono, email):
        """Aggiunge nuovo contatto"""
        contatto = {
            "nome": nome,
            "telefono": telefono,
            "email": email
        }
        self.contatti.append(contatto)
        self.salva_rubrica()
        print(f"✓ Contatto {nome} aggiunto")
    
    def cerca_contatto(self, nome):
        """Cerca contatto per nome"""
        risultati = [c for c in self.contatti if nome.lower() in c['nome'].lower()]
        return risultati
    
    def elimina_contatto(self, nome):
        """Elimina contatto per nome"""
        self.contatti = [c for c in self.contatti if c['nome'] != nome]
        self.salva_rubrica()
        print(f"✓ Contatto {nome} eliminato")
    
    def mostra_tutti(self):
        """Visualizza tutti i contatti"""
        if not self.contatti:
            print("Rubrica vuota")
            return
        
        print(f"\n📱 Rubrica ({len(self.contatti)} contatti):")
        print("-" * 60)
        for i, contatto in enumerate(self.contatti, 1):
            print(f"{i}. {contatto['nome']}")
            print(f"   📞 {contatto['telefono']}")
            print(f"   ✉️  {contatto['email']}")
            print()

# Esempio utilizzo
rubrica = Rubrica()

# Aggiungi contatti
rubrica.aggiungi_contatto("Alice Rossi", "+39 123 456 7890", "alice@email.com")
rubrica.aggiungi_contatto("Bob Verdi", "+39 098 765 4321", "bob@email.com")
rubrica.aggiungi_contatto("Charlie Bianchi", "+39 111 222 3333", "charlie@email.com")

# Mostra tutti
rubrica.mostra_tutti()

# Cerca
risultati = rubrica.cerca_contatto("Alice")
print(f"Trovati {len(risultati)} contatti per 'Alice'")

2 Sistema di Gestione Biblioteca

Obiettivo: Creare un sistema per gestire prestiti di libri con validazione JSON Schema.

Funzionalità: Aggiungere libri, registrare prestiti, restituzioni, report statistici.

💡 Soluzione (Schema incluso)

📚 Sistema Biblioteca JSON
Python
import json
from datetime import datetime, timedelta
from jsonschema import validate, ValidationError

# Schema validazione libro
SCHEMA_LIBRO = {
    "type": "object",
    "properties": {
        "isbn": {"type": "string", "pattern": "^[0-9]{13}$"},
        "titolo": {"type": "string", "minLength": 1},
        "autore": {"type": "string", "minLength": 1},
        "anno": {"type": "integer", "minimum": 1000, "maximum": 2100},
        "disponibile": {"type": "boolean"}
    },
    "required": ["isbn", "titolo", "autore", "anno"]
}

class Biblioteca:
    def __init__(self):
        self.libri = []
        self.prestiti = []
    
    def aggiungi_libro(self, libro):
        """Aggiunge libro con validazione schema"""
        try:
            validate(instance=libro, schema=SCHEMA_LIBRO)
            libro['disponibile'] = True
            self.libri.append(libro)
            print(f"✓ Libro '{libro['titolo']}' aggiunto")
            return True
        except ValidationError as e:
            print(f"❌ Errore validazione: {e.message}")
            return False
    
    def presta_libro(self, isbn, utente):
        """Registra prestito libro"""
        libro = next((l for l in self.libri if l['isbn'] == isbn), None)
        
        if not libro:
            print("❌ Libro non trovato")
            return False
        
        if not libro['disponibile']:
            print("❌ Libro già in prestito")
            return False
        
        # Registra prestito
        prestito = {
            "isbn": isbn,
            "utente": utente,
            "data_prestito": datetime.now().isoformat(),
            "data_restituzione_prevista": (datetime.now() + timedelta(days=14)).isoformat(),
            "restituito": False
        }
        
        self.prestiti.append(prestito)
        libro['disponibile'] = False
        print(f"✓ Libro '{libro['titolo']}' prestato a {utente}")
        return True
    
    def restituisci_libro(self, isbn):
        """Registra restituzione libro"""
        prestito = next((p for p in self.prestiti 
                        if p['isbn'] == isbn and not p['restituito']), None)
        
        if not prestito:
            print("❌ Nessun prestito attivo per questo libro")
            return False
        
        prestito['restituito'] = True
        prestito['data_restituzione_effettiva'] = datetime.now().isoformat()
        
        libro = next(l for l in self.libri if l['isbn'] == isbn)
        libro['disponibile'] = True
        
        print(f"✓ Libro '{libro['titolo']}' restituito")
        return True
    
    def report_prestiti(self):
        """Genera report prestiti attivi"""
        attivi = [p for p in self.prestiti if not p['restituito']]
        
        print(f"\n📊 Report Prestiti Attivi ({len(attivi)}):")
        print("-" * 70)
        
        for prestito in attivi:
            libro = next(l for l in self.libri if l['isbn'] == prestito['isbn'])
            data_prev = datetime.fromisoformat(prestito['data_restituzione_prevista'])
            giorni_rimasti = (data_prev - datetime.now()).days
            
            print(f"📖 {libro['titolo']}")
            print(f"   Utente: {prestito['utente']}")
            print(f"   Restituzione prevista: {data_prev.date()}")
            print(f"   Giorni rimasti: {giorni_rimasti}")
            if giorni_rimasti < 0:
                print(f"   ⚠️  IN RITARDO di {-giorni_rimasti} giorni")
            print()
    
    def salva_database(self, percorso='biblioteca.json'):
        """Salva database su file"""
        dati = {
            "libri": self.libri,
            "prestiti": self.prestiti,
            "timestamp": datetime.now().isoformat()
        }
        
        with open(percorso, 'w', encoding='utf-8') as f:
            json.dump(dati, f, indent=4, ensure_ascii=False)

# Esempio utilizzo
biblioteca = Biblioteca()

# Aggiungi libri
biblioteca.aggiungi_libro({
    "isbn": "9788804671664",
    "titolo": "Il Nome della Rosa",
    "autore": "Umberto Eco",
    "anno": 1980
})

biblioteca.aggiungi_libro({
    "isbn": "9788806219864",
    "titolo": "Se questo è un uomo",
    "autore": "Primo Levi",
    "anno": 1947
})

# Prestiti
biblioteca.presta_libro("9788804671664", "Mario Rossi")
biblioteca.presta_libro("9788806219864", "Anna Verdi")

# Report
biblioteca.report_prestiti()

# Salva
biblioteca.salva_database()

3 Analisi Log JSON da API

Obiettivo: Processare log di API in formato JSON ed estrarre statistiche.

💡 Soluzione con Analisi Statistica

📊 Analizzatore Log JSON
Python
import json
from collections import Counter, defaultdict
from datetime import datetime

class AnalizzatoreLog:
    def __init__(self, percorso_log):
        self.percorso_log = percorso_log
        self.log_entries = self.carica_log()
    
    def carica_log(self):
        """Carica log JSON (formato: array di oggetti)"""
        with open(self.percorso_log, 'r', encoding='utf-8') as f:
            return json.load(f)
    
    def analizza_status_codes(self):
        """Conta occorrenze status code"""
        status_codes = [entry['status_code'] for entry in self.log_entries]
        conteggi = Counter(status_codes)
        
        print("\n📈 Distribuzione Status Codes:")
        print("-" * 40)
        for code, count in sorted(conteggi.items()):
            percentuale = (count / len(status_codes)) * 100
            print(f"  {code}: {count:>5} ({percentuale:>5.1f}%)")
    
    def trova_endpoint_lenti(self, soglia_ms=1000):
        """Trova endpoint con tempo risposta > soglia"""
        lenti = []
        
        for entry in self.log_entries:
            if entry['response_time_ms'] > soglia_ms:
                lenti.append({
                    'endpoint': entry['endpoint'],
                    'tempo': entry['response_time_ms'],
                    'timestamp': entry['timestamp']
                })
        
        print(f"\n⏱️  Endpoint Lenti (>{soglia_ms}ms): {len(lenti)}")
        print("-" * 60)
        
        # Ordina per tempo decrescente
        for req in sorted(lenti, key=lambda x: x['tempo'], reverse=True)[:10]:
            print(f"  {req['endpoint']:30} {req['tempo']:>6}ms")
    
    def analizza_errori(self):
        """Analizza errori (status >= 400)"""
        errori = [e for e in self.log_entries if e['status_code'] >= 400]
        
        print(f"\n❌ Analisi Errori: {len(errori)} totali")
        print("-" * 60)
        
        # Raggruppa per endpoint
        errori_per_endpoint = defaultdict(list)
        for errore in errori:
            errori_per_endpoint[errore['endpoint']].append(errore['status_code'])
        
        # Top 5 endpoint con più errori
        print("\nTop 5 Endpoint con più Errori:")
        top_errori = sorted(
            errori_per_endpoint.items(),
            key=lambda x: len(x[1]),
            reverse=True
        )[:5]
        
        for endpoint, codes in top_errori:
            print(f"  {endpoint:30} {len(codes):>4} errori")
            print(f"    Codes: {Counter(codes)}")
    
    def calcola_metriche_globali(self):
        """Calcola metriche aggregate"""
        tempi = [e['response_time_ms'] for e in self.log_entries]
        
        print("\n📊 Metriche Globali:")
        print("-" * 40)
        print(f"  Richieste totali:     {len(self.log_entries):>8}")
        print(f"  Tempo medio risposta: {sum(tempi)/len(tempi):>8.2f}ms")
        print(f"  Tempo minimo:         {min(tempi):>8}ms")
        print(f"  Tempo massimo:        {max(tempi):>8}ms")
        print(f"  Mediana:              {sorted(tempi)[len(tempi)//2]:>8}ms")
        
        # Percentili
        tempi_ordinati = sorted(tempi)
        p95 = tempi_ordinati[int(len(tempi) * 0.95)]
        p99 = tempi_ordinati[int(len(tempi) * 0.99)]
        
        print(f"  95° Percentile:       {p95:>8}ms")
        print(f"  99° Percentile:       {p99:>8}ms")
    
    def genera_report(self):
        """Genera report completo"""
        print("\n" + "=" * 70)
        print(" 📋 REPORT ANALISI LOG API ".center(70))
        print("=" * 70)
        
        self.calcola_metriche_globali()
        self.analizza_status_codes()
        self.trova_endpoint_lenti(1000)
        self.analizza_errori()
        
        print("\n" + "=" * 70)

# Esempio log simulato
log_esempio = [
    {"timestamp": "2024-11-05T10:00:00", "endpoint": "/api/users", "status_code": 200, "response_time_ms": 45},
    {"timestamp": "2024-11-05T10:00:01", "endpoint": "/api/products", "status_code": 200, "response_time_ms": 1250},
    {"timestamp": "2024-11-05T10:00:02", "endpoint": "/api/users", "status_code": 404, "response_time_ms": 12},
    {"timestamp": "2024-11-05T10:00:03", "endpoint": "/api/orders", "status_code": 500, "response_time_ms": 3000},
    # ... molti altri log ...
]

# Salva log esempio
with open('api_log.json', 'w') as f:
    json.dump(log_esempio, f, indent=2)

# Analizza
analizzatore = AnalizzatoreLog('api_log.json')
analizzatore.genera_report()

9. Progetto Finale: Database JSON Completo

Creiamo un database NoSQL semplificato che usa JSON come formato di storage, ispirato a MongoDB. Include collezioni, query, indici, aggregazioni e backup.

🎯 Caratteristiche del Progetto

  • ✅ Gestione collezioni (come tabelle SQL)
  • ✅ CRUD completo (Create, Read, Update, Delete)
  • ✅ Query con operatori ($gt, $lt, $in, ecc.)
  • ✅ Aggregazione dati (match, sort, limit, project)
  • ✅ Sistema backup/restore
  • ✅ Statistiche e metriche
  • ✅ Persistenza su file JSON
🗄️ JSONDatabase - Implementazione Completa (Parte 1/2)
Python
import json
import uuid
from datetime import datetime
from typing import Dict, List, Any, Optional
from pathlib import Path

class JSONDatabase:
    """Database NoSQL che usa JSON per lo storage"""
    
    def __init__(self, db_path: str = "database.json"):
        self.db_path = Path(db_path)
        self.data = self._initialize_db()
        self.transaction_log = []
    
    def _initialize_db(self) -> Dict:
        """Inizializza o carica database"""
        if self.db_path.exists():
            with open(self.db_path, 'r', encoding='utf-8') as f:
                return json.load(f)
        
        # Struttura database vuoto
        return {
            "metadata": {
                "created": datetime.now().isoformat(),
                "version": "1.0"
            },
            "collections": {}
        }
    
    def save(self):
        """Salva database su disco"""
        self.data["metadata"]["last_modified"] = datetime.now().isoformat()
        
        with open(self.db_path, 'w', encoding='utf-8') as f:
            json.dump(self.data, f, indent=2, ensure_ascii=False)
    
    def create_collection(self, name: str) -> bool:
        """Crea nuova collezione"""
        if name in self.data["collections"]:
            print(f"⚠️  Collezione '{name}' già esistente")
            return False
        
        self.data["collections"][name] = {
            "documents": {},
            "metadata": {
                "created": datetime.now().isoformat(),
                "count": 0
            }
        }
        self.save()
        print(f"✓ Collezione '{name}' creata")
        return True
    
    def drop_collection(self, name: str) -> bool:
        """Elimina collezione"""
        if name not in self.data["collections"]:
            print(f"❌ Collezione '{name}' non trovata")
            return False
        
        del self.data["collections"][name]
        self.save()
        print(f"✓ Collezione '{name}' eliminata")
        return True
    
    def insert(self, collection: str, document: Dict) -> Optional[str]:
        """Inserisce documento in collezione"""
        if collection not in self.data["collections"]:
            self.create_collection(collection)
        
        # Genera ID univoco
        doc_id = str(uuid.uuid4())
        
        # Aggiungi metadata
        document["_id"] = doc_id
        document["_created_at"] = datetime.now().isoformat()
        document["_updated_at"] = datetime.now().isoformat()
        
        # Salva documento
        self.data["collections"][collection]["documents"][doc_id] = document
        self.data["collections"][collection]["metadata"]["count"] += 1
        
        self.save()
        self._log_transaction("INSERT", collection, doc_id)
        
        return doc_id
    
    def find(self, collection: str, query: Dict = None) -> List[Dict]:
        """Trova documenti che matchano query"""
        if collection not in self.data["collections"]:
            return []
        
        documents = self.data["collections"][collection]["documents"].values()
        
        if query is None:
            return list(documents)
        
        # Filtra documenti
        risultati = []
        for doc in documents:
            if self._match_query(doc, query):
                risultati.append(doc)
        
        return risultati
    
    def find_one(self, collection: str, query: Dict) -> Optional[Dict]:
        """Trova primo documento che matcha query"""
        risultati = self.find(collection, query)
        return risultati[0] if risultati else None
🗄️ JSONDatabase - Implementazione Completa (Parte 2/2)
Python
    def update(self, collection: str, query: Dict, update: Dict) -> int:
        """Aggiorna documenti che matchano query"""
        if collection not in self.data["collections"]:
            return 0
        
        documenti = self.find(collection, query)
        count = 0
        
        for doc in documenti:
            doc_id = doc["_id"]
            
            # Applica update
            for key, value in update.items():
                doc[key] = value
            
            doc["_updated_at"] = datetime.now().isoformat()
            
            # Salva modifiche
            self.data["collections"][collection]["documents"][doc_id] = doc
            self._log_transaction("UPDATE", collection, doc_id)
            count += 1
        
        if count > 0:
            self.save()
        
        return count
    
    def delete(self, collection: str, query: Dict) -> int:
        """Elimina documenti che matchano query"""
        if collection not in self.data["collections"]:
            return 0
        
        documenti = self.find(collection, query)
        count = 0
        
        for doc in documenti:
            doc_id = doc["_id"]
            del self.data["collections"][collection]["documents"][doc_id]
            self.data["collections"][collection]["metadata"]["count"] -= 1
            self._log_transaction("DELETE", collection, doc_id)
            count += 1
        
        if count > 0:
            self.save()
        
        return count
    
    def _match_query(self, document: Dict, query: Dict) -> bool:
        """Verifica se documento corrisponde a query"""
        for key, value in query.items():
            if key not in document:
                return False
            
            # Supporto operatori
            if isinstance(value, dict):
                doc_value = document[key]
                
                if "$gt" in value and not doc_value > value["$gt"]:
                    return False
                if "$lt" in value and not doc_value < value["$lt"]:
                    return False
                if "$gte" in value and not doc_value >= value["$gte"]:
                    return False
                if "$lte" in value and not doc_value <= value["$lte"]:
                    return False
                if "$ne" in value and doc_value == value["$ne"]:
                    return False
                if "$in" in value and doc_value not in value["$in"]:
                    return False
            else:
                if document[key] != value:
                    return False
        
        return True
    
    def aggregate(self, collection: str, pipeline: List[Dict]) -> List[Any]:
        """Aggregazione semplificata"""
        documents = self.find(collection)
        
        for stage in pipeline:
            if "$match" in stage:
                documents = [d for d in documents if self._match_query(d, stage["$match"])]
            elif "$sort" in stage:
                field = list(stage["$sort"].keys())[0]
                reverse = stage["$sort"][field] == -1
                documents.sort(key=lambda x: x.get(field), reverse=reverse)
            elif "$limit" in stage:
                documents = documents[:stage["$limit"]]
            elif "$project" in stage:
                projected = []
                for doc in documents:
                    new_doc = {}
                    for field, include in stage["$project"].items():
                        if include and field in doc:
                            new_doc[field] = doc[field]
                    projected.append(new_doc)
                documents = projected
        
        return documents
    
    def backup(self, backup_path: str = None):
        """Crea backup del database"""
        backup_path = backup_path or f"backup_{datetime.now():%Y%m%d_%H%M%S}.json"
        
        with open(backup_path, 'w', encoding='utf-8') as f:
            json.dump({
                "data": self.data,
                "metadata": {
                    "backup_date": datetime.now().isoformat(),
                    "transaction_count": len(self.transaction_log),
                    "collections": list(self.data["collections"].keys())
                }
            }, f, indent=2, ensure_ascii=False)
        
        return backup_path
    
    def stats(self) -> Dict:
        """Statistiche database"""
        stats = {
            "collections": {},
            "total_documents": 0,
            "total_size_bytes": len(json.dumps(self.data).encode('utf-8'))
        }
        
        for name, collection in self.data["collections"].items():
            doc_count = collection["metadata"]["count"]
            stats["collections"][name] = {
                "count": doc_count,
                "created": collection["metadata"]["created"]
            }
            stats["total_documents"] += doc_count
        
        return stats
    
    def _log_transaction(self, operation: str, collection: str, doc_id: str):
        """Log transazione per audit"""
        self.transaction_log.append({
            "timestamp": datetime.now().isoformat(),
            "operation": operation,
            "collection": collection,
            "document_id": doc_id
        })

# ========================================
# ESEMPIO DI UTILIZZO COMPLETO
# ========================================

db = JSONDatabase("test_db.json")

# Crea collezione
db.create_collection("users")

# Inserisci documenti
user1_id = db.insert("users", {
    "name": "Alice",
    "email": "alice@example.com",
    "age": 30,
    "active": True
})

user2_id = db.insert("users", {
    "name": "Bob",
    "email": "bob@example.com",
    "age": 25,
    "active": True
})

user3_id = db.insert("users", {
    "name": "Charlie",
    "email": "charlie@example.com",
    "age": 35,
    "active": False
})

print("\n📝 Documenti inseriti\n")

# Query
print("👥 Tutti gli utenti:")
all_users = db.find("users")
for user in all_users:
    print(f"  - {user['name']} ({user['email']})")

print("\n🔍 Utenti attivi con età > 26:")
active_users = db.find("users", {
    "active": True,
    "age": {"$gt": 26}
})
for user in active_users:
    print(f"  - {user['name']}, età: {user['age']}")

# Aggregazione
print("\n📊 Utenti ordinati per età (desc), limitati a 2:")
result = db.aggregate("users", [
    {"$match": {"active": True}},
    {"$sort": {"age": -1}},
    {"$limit": 2},
    {"$project": {"name": 1, "age": 1}}
])
print(json.dumps(result, indent=2))

# Statistiche
print("\n📈 Statistiche database:")
print(json.dumps(db.stats(), indent=2))

# Backup
backup_file = db.backup()
print(f"\n💾 Backup creato: {backup_file}")

🎓 Cosa Abbiamo Imparato

Questo progetto dimostra:

  • Persistenza: salvataggio automatico su disco
  • CRUD completo: tutte le operazioni base database
  • Query avanzate: supporto operatori di confronto
  • Aggregazioni: pipeline per analisi dati complesse
  • Metadata: timestamp automatici, ID univoci
  • Backup: sistema disaster recovery
  • Logging: audit trail delle operazioni

10. Riferimenti e Risorse

📚 Documentazione Ufficiale

🎯 Strumenti Utili

  • JSONLint: Validatore online JSON (jsonlint.com)
  • JSON Formatter: Formattazione e visualizzazione
  • Postman: Test API che restituiscono JSON
  • jq: Command-line JSON processor (stedolan.github.io/jq/)
  • JSON Viewer: Estensioni browser per visualizzare JSON

Librerie Python Consigliate

Libreria Uso Installazione Performance
orjson Parsing velocissimo pip install orjson ⚡⚡⚡⚡⚡
ujson Alternative veloce pip install ujson ⚡⚡⚡⚡
jsonschema Validazione schema pip install jsonschema ⚡⚡⚡
jsonpath-ng Query JSONPath pip install jsonpath-ng ⚡⚡⚡
ijson Streaming JSON pip install ijson ⚡⚡
pydantic Data validation pip install pydantic ⚡⚡⚡⚡

Checklist Finale: Cosa Hai Imparato

✅ Competenze Acquisite

  • ☑️ Sintassi JSON: comprensione completa di oggetti, array e tipi di dati
  • ☑️ Validazione: identificare e risolvere errori sintattici comuni
  • ☑️ Python json module: serializzazione/deserializzazione (dumps/loads, dump/load)
  • ☑️ Gestione file: lettura e scrittura JSON sicure con gestione errori
  • ☑️ Encoder/Decoder custom: gestione tipi Python complessi (datetime, Decimal, set)
  • ☑️ Streaming: processare file JSON enormi con ijson
  • ☑️ Performance: librerie alternative (orjson, ujson) per speed-up
  • ☑️ JSON Schema: validazione strutturale e contratti dati
  • ☑️ Best practices: sicurezza, naming conventions, gestione null
  • ☑️ API REST: consumare e produrre JSON via HTTP
  • ☑️ Database JSON: implementazione completa CRUD con query e aggregazioni

🎓 Prossimi Passi

Ora che padroneggi JSON, ecco dove approfondire:

  • GraphQL: alternativa moderna a REST per query flessibili
  • Protocol Buffers: formato binario di Google per performance estreme
  • MessagePack: JSON binario più compatto
  • MongoDB: database NoSQL che usa BSON (Binary JSON)
  • Elasticsearch: motore ricerca full-text su documenti JSON
  • FastAPI: framework Python per API moderne con validazione JSON automatica

💬 Hai Domande o Feedback?

Questa guida è stata creata con l'obiettivo di fornire una formazione completa e pratica su JSON. Se hai domande, suggerimenti o vuoi approfondire particolari argomenti, non esitare a contattare il Prof. Carello.

📧 Email: info@nicolocarello.it