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
{
"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
- Coppie chiave-valore: i dati sono organizzati in coppie
"chiave": valore - Chiavi sempre tra virgolette doppie:
"nome"è valido,'nome'onomenon lo sono - Virgole per separare: elementi in array e proprietà in oggetti
- NO virgola finale: l'ultimo elemento non deve avere virgola dopo
- Graffe per oggetti:
{ } - Quadre per array:
[ ] - Sensibile a maiuscole/minuscole:
true≠True
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.
// 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 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:
// ❌ 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": nullche 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 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.
{
// 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).
{
"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 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:
// 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.
{
"$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.
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.
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
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()
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()
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.
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:
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.
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
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)
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
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
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
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
- JSON.org: www.json.org - Specifiche ufficiali del formato JSON
- RFC 8259: Standard IETF per JSON (Dicembre 2017)
- Python json module: docs.python.org/3/library/json.html
- JSON Schema: json-schema.org per validazione
🎯 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