Supporto JSON in Delphi: Guida Completa con Esempi (2026)
🇬🇧 English • 🇪🇸 Español • 🇩🇪 Deutsch • 🇫🇷 Français
JSON è lo standard de-facto per scambiare dati tra applicazioni. Che tu stia costruendo REST API, leggendo file di configurazione o parlando con un web service, ti serve sapere come funziona in Delphi.
Questa guida copre il supporto JSON in Delphi da cima a fondo, con esempi compilabili che puoi riusare nei tuoi progetti.
TJSONObject, TJSONArray). Per il parsing in stile streaming/SAX, consulta le unit System.JSON.Readers e System.JSON.Writers.
Compatibilità tra le Versioni di Delphi
Il supporto JSON si è evoluto significativamente tra le varie versioni di Delphi:
| Versione | Unit | Caratteristiche Principali |
|---|---|---|
| Delphi 2009 | DBXJSON |
Supporto JSON iniziale con classi base |
| Delphi XE6 | System.JSON |
Unit rinominata, API migliorata |
| Delphi 10.1 Berlin | System.JSON |
API fluent TJSONObjectBuilder, miglioramenti a TryGetValue<T> |
| Delphi 10.3 Rio | System.JSON |
Metodo Format(), EJSONParseException con dettagli, miglioramenti delle prestazioni |
| Delphi 11-12 | System.JSON |
Ulteriori ottimizzazioni e perfezionamenti |
| Delphi 13 Florence | System.JSON |
Ultimi miglioramenti e supporto continuo |
Cos’è JSON?
JSON è un formato di scambio dati leggero, basato su testo. Lo leggono e scrivono facilmente gli umani, lo analizzano e generano altrettanto facilmente le macchine. Un documento JSON può contenere:
- Oggetti: Coppie chiave-valore racchiuse tra parentesi graffe
{} - Array: Liste ordinate di valori racchiuse tra parentesi quadre
[] - Valori: Stringhe, numeri, booleani (
true/false),null, oggetti o array
Esempio di struttura JSON:
{
"name": "Daniele Teti",
"age": 45,
"active": true,
"skills": ["Delphi", "Python", "SQL"],
"address": {
"city": "Rome",
"country": "Italy"
}
}
Panoramica delle Classi JSON di Delphi
Delphi fornisce supporto JSON integrato tramite l’unit System.JSON. Le classi principali sono:
| Classe | Descrizione |
|---|---|
TJSONValue |
Classe base per tutti i tipi di valori JSON |
TJSONObject |
Rappresenta un oggetto JSON (coppie chiave-valore) |
TJSONArray |
Rappresenta un array JSON (lista ordinata) |
TJSONString |
Rappresenta un valore stringa JSON |
TJSONNumber |
Rappresenta un valore numerico JSON |
TJSONBool |
Rappresenta un valore booleano JSON |
TJSONNull |
Rappresenta un valore null JSON |
TJSONPair |
Rappresenta una coppia chiave-valore in un oggetto |
Creazione di Oggetti JSON
Il punto di partenza: creare oggetti JSON e aggiungere proprietà.
Creazione Base di Oggetti JSON
La base è creare un TJSONObject e aggiungere coppie chiave-valore. AddPair ha overload per stringhe, interi, booleani e double: non devi avvolgere i valori primitivi in classi JSON apposite. Ecco un oggetto con qualche tipo diverso:
program JSONCreateBasic;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LJSONObject: TJSONObject;
begin
LJSONObject := TJSONObject.Create;
try
// Aggiungi proprietà stringa
LJSONObject.AddPair('firstName', 'Daniele');
LJSONObject.AddPair('lastName', 'Teti');
// Aggiungi proprietà numerica (sono disponibili overload per Integer, Int64, Double)
LJSONObject.AddPair('age', 45);
// Aggiungi proprietà booleana
LJSONObject.AddPair('active', True);
// Aggiungi proprietà null (nessun overload - deve usare TJSONNull)
LJSONObject.AddPair('middleName', TJSONNull.Create);
// Stampa il JSON
// Nota: Format() disponibile da Delphi 10.3 Rio
{$IF CompilerVersion >= 33.0} // Delphi 10.3 Rio
WriteLn(LJSONObject.Format());
{$ELSE}
WriteLn(LJSONObject.ToString);
{$ENDIF}
finally
LJSONObject.Free;
end;
ReadLn;
end.
Output:
{
"firstName": "Daniele",
"lastName": "Teti",
"age": 45,
"active": true,
"middleName": null
}
Creazione di Array JSON
Un array JSON è una collezione ordinata: stringhe, numeri, booleani, anche altri array o oggetti, mescolati come vuoi. Quando aggiungi un TJSONArray a un TJSONObject con AddPair, l’oggetto genitore ne prende possesso: liberi solo la root. Qui costruisco un array di stringhe e uno a tipo misto:
program JSONCreateArray;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LJSONObject: TJSONObject;
LContacts: TJSONArray;
LSkills: TJSONArray;
begin
LJSONObject := TJSONObject.Create;
try
LJSONObject.AddPair('name', 'Daniele Teti');
// Crea array di stringhe
LSkills := TJSONArray.Create;
LJSONObject.AddPair('skills', LSkills);
LSkills.Add('Delphi');
LSkills.Add('Python');
LSkills.Add('SQL');
// Crea array con tipi misti
LContacts := TJSONArray.Create;
LJSONObject.AddPair('contacts', LContacts);
LContacts.Add('daniele@example.com'); // stringa
LContacts.Add(123456); // numero
LContacts.Add(True); // booleano
{$IF CompilerVersion >= 33.0}
WriteLn(LJSONObject.Format());
{$ELSE}
WriteLn(LJSONObject.ToString);
{$ENDIF}
finally
LJSONObject.Free; // Libera anche LContacts e LSkills
end;
ReadLn;
end.
Output:
{
"name": "Daniele Teti",
"skills": [
"Delphi",
"Python",
"SQL"
],
"contacts": [
"daniele@example.com",
123456,
true
]
}
Creazione di un Array di Oggetti
Il pattern più comune nel JSON vero è un array di oggetti: una lista di utenti, di prodotti, di record. Ogni oggetto ha le sue proprietà. Crei ogni oggetto a parte e lo aggiungi all’array con Add; l’array possiede tutti quelli che gli passi:
program JSONArrayOfObjects;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LRoot: TJSONObject;
LUsers: TJSONArray;
LUser: TJSONObject;
begin
LRoot := TJSONObject.Create;
try
LUsers := TJSONArray.Create;
LRoot.AddPair('users', LUsers);
// Primo utente
LUser := TJSONObject.Create;
LUsers.Add(LUser);
LUser.AddPair('id', 1);
LUser.AddPair('name', 'Alice');
LUser.AddPair('email', 'alice@example.com');
// Secondo utente
LUser := TJSONObject.Create;
LUsers.Add(LUser);
LUser.AddPair('id', 2);
LUser.AddPair('name', 'Bob');
LUser.AddPair('email', 'bob@example.com');
// Terzo utente
LUser := TJSONObject.Create;
LUsers.Add(LUser);
LUser.AddPair('id', 3);
LUser.AddPair('name', 'Charlie');
LUser.AddPair('email', 'charlie@example.com');
{$IF CompilerVersion >= 33.0}
WriteLn(LRoot.Format());
{$ELSE}
WriteLn(LRoot.ToString);
{$ENDIF}
finally
LRoot.Free;
end;
ReadLn;
end.
Output:
{
"users": [
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
},
{
"id": 2,
"name": "Bob",
"email": "bob@example.com"
},
{
"id": 3,
"name": "Charlie",
"email": "charlie@example.com"
}
]
}
Oggetti JSON Annidati
I dati complessi sono quasi sempre gerarchici: una persona ha un indirizzo, l’indirizzo ha città e paese. In Delphi annidi le strutture aggiungendo TJSONObject come valori dentro altri oggetti. Come per gli array, il genitore possiede i figli, e la gestione della memoria resta semplice. Qui una persona con indirizzo e azienda annidati:
program JSONNested;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LJSONObject: TJSONObject;
LAddress: TJSONObject;
LCompany: TJSONObject;
begin
LJSONObject := TJSONObject.Create;
try
LJSONObject.AddPair('name', 'Daniele Teti');
// Crea oggetto indirizzo annidato
LAddress := TJSONObject.Create;
LJSONObject.AddPair('address', LAddress);
LAddress.AddPair('street', 'Via Roma 123');
LAddress.AddPair('city', 'Rome');
LAddress.AddPair('country', 'Italy');
LAddress.AddPair('zipCode', '00100');
// Crea un altro oggetto annidato
LCompany := TJSONObject.Create;
LJSONObject.AddPair('company', LCompany);
LCompany.AddPair('name', 'bit Time Professionals');
LCompany.AddPair('website', 'https://www.bittime.it');
{$IF CompilerVersion >= 33.0}
WriteLn(LJSONObject.Format());
{$ELSE}
WriteLn(LJSONObject.ToString);
{$ENDIF}
finally
LJSONObject.Free;
end;
ReadLn;
end.
Output:
{
"name": "Daniele Teti",
"address": {
"street": "Via Roma 123",
"city": "Rome",
"country": "Italy",
"zipCode": "00100"
},
"company": {
"name": "bit Time Professionals",
"website": "https://www.bittime.it"
}
}
Uso di TJSONObjectBuilder (Delphi 10.1 Berlin+)
Se preferisci una sintassi fluent, Delphi 10.1 Berlin ha introdotto TJSONObjectBuilder: costruisci tutta la struttura in un’unica espressione concatenando BeginObject, BeginArray, Add, EndObject, EndArray. Il builder scrive su un TJsonTextWriter, che scrive su un TStringBuilder. La configurazione iniziale è più verbosa, ma su strutture complesse il risultato si legge meglio:
program JSONBuilderExample;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.Classes,
System.JSON.Types,
System.JSON.Writers,
System.JSON.Builders;
var
LBuilder: TJSONObjectBuilder;
LWriter: TJsonTextWriter;
LStringWriter: TStringWriter;
LStringBuilder: TStringBuilder;
begin
LStringBuilder := TStringBuilder.Create;
try
LStringWriter := TStringWriter.Create(LStringBuilder);
try
LWriter := TJsonTextWriter.Create(LStringWriter);
try
LWriter.Formatting := TJsonFormatting.Indented;
LBuilder := TJSONObjectBuilder.Create(LWriter);
try
// Costruisci JSON usando l'API fluent
LBuilder
.BeginObject
.Add('firstName', 'Daniele')
.Add('lastName', 'Teti')
.Add('age', 45)
.Add('active', True)
.BeginObject('address')
.Add('city', 'Rome')
.Add('country', 'Italy')
.EndObject
.BeginArray('skills')
.Add('Delphi')
.Add('Python')
.Add('SQL')
.EndArray
.EndObject;
WriteLn(LStringBuilder.ToString);
finally
LBuilder.Free;
end;
finally
LWriter.Free;
end;
finally
LStringWriter.Free;
end;
finally
LStringBuilder.Free;
end;
ReadLn;
end.
Output:
{
"firstName": "Daniele",
"lastName": "Teti",
"age": 45,
"active": true,
"address": {
"city": "Rome",
"country": "Italy"
},
"skills": [
"Delphi",
"Python",
"SQL"
]
}
Parsing di Stringhe JSON
JSON che arriva da un web service, un file o qualunque altra fonte va analizzato in oggetti Delphi prima di poterci lavorare. Se ne occupa TJSONObject.ParseJSONValue, un metodo di classe che restituisce un TJSONValue, la classe base: controlla tu se è del tipo che ti aspetti, tipicamente TJSONObject o TJSONArray. Su JSON malformato restituisce nil, quindi controllalo sempre prima di andare avanti:
program JSONParsing;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
JSON_STRING =
'{"name":"Daniele","age":45,"skills":["Delphi","Python"]}';
var
LJSONValue: TJSONValue;
LJSONObject: TJSONObject;
begin
// ParseJSONValue restituisce TJSONValue, converti al tipo appropriato
LJSONValue := TJSONObject.ParseJSONValue(JSON_STRING);
if LJSONValue = nil then
begin
WriteLn('ERRORE: JSON non valido!');
ReadLn;
Exit;
end;
try
// Controlla se è un oggetto (potrebbe essere un array a livello root)
if not (LJSONValue is TJSONObject) then
begin
WriteLn('ERRORE: Atteso oggetto JSON a livello root');
Exit;
end;
LJSONObject := TJSONObject(LJSONValue); // Cast diretto - sicuro dopo il controllo "is"
WriteLn('Analisi riuscita!');
WriteLn('Numero di coppie: ', LJSONObject.Count);
{$IF CompilerVersion >= 33.0}
WriteLn(LJSONObject.Format());
{$ELSE}
WriteLn(LJSONObject.ToString);
{$ENDIF}
finally
LJSONValue.Free;
end;
ReadLn;
end.
ParseJSONValue restituisce nil, che indica JSON non valido.
Gestione degli Errori di Parsing (Delphi 10.3+)
Quando il parsing fallisce, sapere perché ti fa risparmiare tempo. Da Delphi 10.3 Rio, passa True come secondo parametro a ParseJSONValue e ottieni un’eccezione EJSONParseException invece di un nil muto. Dentro trovi il messaggio di errore, il percorso dove ha fallito e l’offset del carattere: utile su JSON complesso o arrivato da fuori:
program JSONParseErrors;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
INVALID_JSON = '{"name": "Test", "value": }'; // Non valido!
var
LJSONValue: TJSONValue;
begin
{$IF CompilerVersion >= 33.0} // Delphi 10.3 Rio
try
// Usa l'opzione RaiseExc per ottenere l'eccezione con i dettagli
LJSONValue := TJSONObject.ParseJSONValue(INVALID_JSON, True);
try
WriteLn('Analizzato: ', LJSONValue.ToString);
finally
LJSONValue.Free;
end;
except
on E: EJSONParseException do
begin
WriteLn('Errore di parsing!');
WriteLn(' Messaggio: ', E.Message);
WriteLn(' Percorso: ', E.Path);
WriteLn(' Offset: ', E.Offset);
end;
end;
{$ELSE}
// Pre-10.3: controlla solo nil
LJSONValue := TJSONObject.ParseJSONValue(INVALID_JSON);
if LJSONValue = nil then
WriteLn('JSON non valido - nessun dettaglio disponibile')
else
LJSONValue.Free;
{$ENDIF}
ReadLn;
end.
Lettura di Valori JSON
Delphi ti dà diversi modi per leggere un valore da un oggetto JSON, ognuno con un compromesso diverso tra comodità e sicurezza. Sapere quale usare quando ti fa scrivere codice che non esplode sui dati mancanti.
Metodo 1: GetValue con Tipo Generico (Delphi XE7+)
Il modo più diretto è GetValue<T>: specifichi il tipo che ti aspetti e Delphi converte per te. Solleva un’eccezione se la chiave non esiste, quindi usalo solo quando sai per certo che c’è:
program JSONReadGetValue;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
JSON_DATA = '{"name":"Daniele","age":45,"active":true}';
var
LJSONObject: TJSONObject;
begin
LJSONObject := TJSONObject.ParseJSONValue(JSON_DATA) as TJSONObject;
try
// GetValue<T> - solleva eccezione se la chiave non è trovata
WriteLn('Nome: ', LJSONObject.GetValue<string>('name'));
WriteLn('Età: ', LJSONObject.GetValue<Integer>('age'));
WriteLn('Attivo: ', LJSONObject.GetValue<Boolean>('active'));
finally
LJSONObject.Free;
end;
ReadLn;
end.
Metodo 2: TryGetValue - Lettura Sicura (Consigliato)
In produzione usa TryGetValue<T>. Restituisce False se la chiave manca o il valore non converte al tipo richiesto, senza passare da un blocco except. Utile con JSON esterno, dove non hai garanzie su quali campi arrivano:
program JSONReadTryGetValue;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
JSON_DATA = '{"name":"Daniele","age":45}';
var
LJSONObject: TJSONObject;
LName: string;
LAge: Integer;
LMiddleName: string;
begin
LJSONObject := TJSONObject.ParseJSONValue(JSON_DATA) as TJSONObject;
try
// TryGetValue restituisce False se la chiave non è trovata (nessuna eccezione)
if LJSONObject.TryGetValue<string>('name', LName) then
WriteLn('Nome: ', LName)
else
WriteLn('Nome non trovato');
if LJSONObject.TryGetValue<Integer>('age', LAge) then
WriteLn('Età: ', LAge)
else
WriteLn('Età non trovata');
// Questa chiave non esiste - nessuna eccezione sollevata
if LJSONObject.TryGetValue<string>('middleName', LMiddleName) then
WriteLn('Secondo Nome: ', LMiddleName)
else
WriteLn('Secondo Nome: (non specificato)');
finally
LJSONObject.Free;
end;
ReadLn;
end.
Metodo 3: FindValue - Restituisce nil se Non Trovato
Se ti serve il TJSONValue grezzo invece di un valore convertito, usa FindValue. Restituisce nil se la chiave non esiste, non solleva mai eccezioni, e ti dà accesso pieno a proprietà e metodi. Comodo quando devi controllare il tipo effettivo di un valore o navigare strutture annidate:
program JSONReadFindValue;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
JSON_DATA = '{"name":"Daniele","age":45}';
var
LJSONObject: TJSONObject;
LValue: TJSONValue;
begin
LJSONObject := TJSONObject.ParseJSONValue(JSON_DATA) as TJSONObject;
try
// FindValue restituisce nil se non trovato (non solleva mai eccezioni)
LValue := LJSONObject.FindValue('name');
if LValue <> nil then
WriteLn('Nome: ', LValue.Value);
LValue := LJSONObject.FindValue('nonexistent');
if LValue = nil then
WriteLn('Chiave "nonexistent" non trovata');
finally
LJSONObject.Free;
end;
ReadLn;
end.
Metodo 4: Notazione a Percorso per Valori Annidati
La notazione a percorso è una delle comodità migliori di Delphi: accedi a valori annidati con un percorso separato da punti, tipo 'person.address.city', senza attraversare a mano ogni oggetto intermedio. Funziona con TryGetValue, GetValue e FindValue:
program JSONReadPath;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
JSON_DATA = '{' +
'"person": {' +
' "name": "Daniele",' +
' "address": {' +
' "city": "Rome",' +
' "country": "Italy"' +
' }' +
'}' +
'}';
var
LJSONObject: TJSONObject;
LValue: string;
begin
LJSONObject := TJSONObject.ParseJSONValue(JSON_DATA) as TJSONObject;
try
// Usa la notazione a punto per accedere ai valori annidati
if LJSONObject.TryGetValue<string>('person.name', LValue) then
WriteLn('Nome Persona: ', LValue);
if LJSONObject.TryGetValue<string>('person.address.city', LValue) then
WriteLn('Città: ', LValue);
if LJSONObject.TryGetValue<string>('person.address.country', LValue) then
WriteLn('Paese: ', LValue);
finally
LJSONObject.Free;
end;
ReadLn;
end.
Lettura di Array - Approcci Classico e Moderno
Su un array devi iterare gli elementi. Delphi supporta sia il ciclo classico per indice con Items[I] sia il for-in, che funziona su qualunque TJSONArray. Il for-in è più pulito quando non ti serve la posizione; il ciclo classico te la dà. Sugli array numerici, converti ogni elemento in TJSONNumber per usare AsInt o AsDouble:
program JSONReadArrays;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
JSON_DATA = '{"skills":["Delphi","Python","SQL"],"scores":[95,87,92]}';
var
LJSONObject: TJSONObject;
LSkills: TJSONArray;
LScores: TJSONArray;
LItem: TJSONValue;
I: Integer;
begin
LJSONObject := TJSONObject.ParseJSONValue(JSON_DATA) as TJSONObject;
try
// Leggi array di stringhe - ciclo for classico
if LJSONObject.TryGetValue<TJSONArray>('skills', LSkills) then
begin
WriteLn('Competenze (ciclo classico):');
for I := 0 to LSkills.Count - 1 do
WriteLn(' ', I + 1, '. ', LSkills.Items[I].Value);
end;
WriteLn;
// Leggi array di stringhe - ciclo for-in moderno (Delphi XE+)
if LJSONObject.TryGetValue<TJSONArray>('skills', LSkills) then
begin
WriteLn('Competenze (ciclo for-in):');
for LItem in LSkills do
WriteLn(' - ', LItem.Value);
end;
WriteLn;
// Leggi array numerico
if LJSONObject.TryGetValue<TJSONArray>('scores', LScores) then
begin
WriteLn('Punteggi:');
for LItem in LScores do
WriteLn(' Punteggio: ', (LItem as TJSONNumber).AsInt);
end;
finally
LJSONObject.Free;
end;
ReadLn;
end.
Iterazione sulle Coppie di un Oggetto JSON
A volte devi scorrere tutte le proprietà di un oggetto senza sapere in anticipo i nomi delle chiavi: un visualizzatore JSON generico, una struttura dinamica. Il for-in funziona su TJSONObject come sugli array, e produce istanze TJSONPair. Ogni coppia ti dà la chiave (JsonString.Value), il valore (JsonValue), e puoi controllare il tipo runtime con ClassName:
program JSONIteratePairs;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
JSON_DATA = '{"name":"Daniele","age":45,"city":"Rome","active":true}';
var
LJSONObject: TJSONObject;
LPair: TJSONPair;
begin
LJSONObject := TJSONObject.ParseJSONValue(JSON_DATA) as TJSONObject;
try
WriteLn('Tutte le coppie nell''oggetto:');
WriteLn;
// Itera su tutte le coppie usando for-in
for LPair in LJSONObject do
begin
WriteLn('Chiave: ', LPair.JsonString.Value);
WriteLn('Valore: ', LPair.JsonValue.ToString);
WriteLn('Tipo: ', LPair.JsonValue.ClassName);
WriteLn;
end;
finally
LJSONObject.Free;
end;
ReadLn;
end.
Modifica di Oggetti JSON
Gli oggetti JSON in Delphi sono mutabili: aggiungi, rimuovi, aggiorni proprietà dopo la creazione. Ma la gestione della memoria ha una regola precisa: RemovePair ti trasferisce la proprietà della coppia rimossa, e tocca a te liberarla. Free in Delphi è sicuro su nil, quindi RemovePair('key').Free funziona anche se la chiave non esiste.
Aggiunta e Rimozione di Coppie
Ecco il ciclo completo: crei proprietà, ne rimuovi una, ne aggiorni un’altra. Per aggiornare un valore rimuovi prima la vecchia coppia (e la liberi), poi ne aggiungi una nuova con la stessa chiave:
program JSONModify;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LJSONObject: TJSONObject;
LRemovedPair: TJSONPair;
begin
LJSONObject := TJSONObject.Create;
try
// Aggiungi coppie iniziali
LJSONObject.AddPair('name', 'Daniele');
LJSONObject.AddPair('city', 'Rome');
LJSONObject.AddPair('temp', 'da rimuovere');
WriteLn('Iniziale:');
WriteLn(LJSONObject.ToString);
WriteLn;
// Rimuovi una coppia - RemovePair restituisce la coppia rimossa (ne hai la proprietà!)
LRemovedPair := LJSONObject.RemovePair('temp');
LRemovedPair.Free; // Sicuro anche se nil - Free controlla Self <> nil
WriteLn('Dopo la rimozione di "temp":');
WriteLn(LJSONObject.ToString);
WriteLn;
// Per aggiornare un valore: rimuovi poi aggiungi
LRemovedPair := LJSONObject.RemovePair('city');
LRemovedPair.Free;
LJSONObject.AddPair('city', 'Milan');
WriteLn('Dopo l''aggiornamento di "city":');
WriteLn(LJSONObject.ToString);
finally
LJSONObject.Free;
end;
ReadLn;
end.
Output:
Iniziale:
{"name":"Daniele","city":"Rome","temp":"da rimuovere"}
Dopo la rimozione di "temp":
{"name":"Daniele","city":"Rome"}
Dopo l'aggiornamento di "city":
{"name":"Daniele","city":"Milan"}
Clonazione di Oggetti JSON
Se devi modificare un oggetto JSON senza toccare l’originale, usa Clone. È una copia profonda: un albero indipendente, dove le modifiche su una copia non si vedono sull’altra. Serve quando ricevi dati che devi trasformare prima di rispedirli, mantenendo intatto l’originale:
program JSONClone;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LOriginal: TJSONObject;
LClone: TJSONObject;
LPair: TJSONPair;
begin
LOriginal := TJSONObject.Create;
try
LOriginal.AddPair('name', 'Daniele');
LOriginal.AddPair('city', 'Rome');
// Clone crea una copia indipendente
LClone := LOriginal.Clone as TJSONObject;
try
// Modifica il clone - l'originale non è influenzato
LPair := LClone.RemovePair('city');
LPair.Free;
LClone.AddPair('city', 'Milan');
WriteLn('Originale: ', LOriginal.ToString);
WriteLn('Clone: ', LClone.ToString);
finally
LClone.Free;
end;
finally
LOriginal.Free;
end;
ReadLn;
end.
Output:
Originale: {"name":"Daniele","city":"Rome"}
Clone: {"name":"Daniele","city":"Milan"}
Lavorare con File JSON
Salvare JSON su disco ti serve per la configurazione, la cache, l’export dei dati. System.IOUtils ti dà TFile, con metodi semplici per leggere e scrivere file di testo: si sposa bene con JSON, che in fondo è solo una stringa.
Salvare JSON su File
Per salvare un oggetto JSON, convertilo in stringa con Format() (leggibile) o ToString() (compatto) e scrivi la stringa su disco. TPath.GetDocumentsPath ti garantisce una posizione scrivibile su qualunque configurazione di Windows:
program JSONSaveToFile;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.IOUtils,
System.JSON;
var
LJSONObject: TJSONObject;
LDatabase: TJSONObject;
LFileName: string;
begin
LFileName := TPath.Combine(TPath.GetDocumentsPath, 'config.json');
LJSONObject := TJSONObject.Create;
try
LJSONObject.AddPair('appName', 'MyApplication');
LJSONObject.AddPair('version', '1.0.0');
LJSONObject.AddPair('debug', False);
LDatabase := TJSONObject.Create;
LJSONObject.AddPair('database', LDatabase);
LDatabase.AddPair('host', 'localhost');
LDatabase.AddPair('port', 5432);
// Salva su file
{$IF CompilerVersion >= 33.0}
TFile.WriteAllText(LFileName, LJSONObject.Format());
{$ELSE}
TFile.WriteAllText(LFileName, LJSONObject.ToString);
{$ENDIF}
WriteLn('Salvato in: ', LFileName);
finally
LJSONObject.Free;
end;
ReadLn;
end.
Caricamento di JSON da File
Leggere è altrettanto semplice: leggi il file in una stringa e la analizzi con ParseJSONValue. Controlla prima che il file esista, per evitare eccezioni, e che il parsing sia riuscito prima di toccare i dati. La notazione a percorso funziona uguale sia su JSON analizzato che su oggetti costruiti a mano:
program JSONLoadFromFile;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.IOUtils,
System.JSON;
var
LJSONObject: TJSONObject;
LJSONValue: TJSONValue;
LContent: string;
LFileName: string;
LAppName: string;
LPort: Integer;
begin
LFileName := TPath.Combine(TPath.GetDocumentsPath, 'config.json');
if not TFile.Exists(LFileName) then
begin
WriteLn('File non trovato: ', LFileName);
ReadLn;
Exit;
end;
LContent := TFile.ReadAllText(LFileName);
LJSONValue := TJSONObject.ParseJSONValue(LContent);
if LJSONValue = nil then
begin
WriteLn('JSON non valido nel file!');
ReadLn;
Exit;
end;
try
LJSONObject := LJSONValue as TJSONObject;
if LJSONObject.TryGetValue<string>('appName', LAppName) then
WriteLn('Nome App: ', LAppName);
if LJSONObject.TryGetValue<Integer>('database.port', LPort) then
WriteLn('Porta Database: ', LPort);
finally
LJSONValue.Free;
end;
ReadLn;
end.
Da oggetti a JSON con REST.Json
Fino a qui il JSON lo hai costruito a mano, coppia per coppia. Ha senso quando il punto è la forma del documento. Non ne ha quando hai già una classe e vuoi solo spedirla in rete.
Per quello Delphi ha REST.Json. Fa parte di Delphi da XE5, non richiede codice di terze parti, e quasi tutto il lavoro lo fa una chiamata sola.
Dall’oggetto al JSON
uses
REST.Json, REST.Json.Types;
type
TAddress = class
private
FCity: string;
FZipCode: string;
public
property City: string read FCity write FCity;
property ZipCode: string read FZipCode write FZipCode;
end;
TCustomer = class
private
FId: Integer;
FName: string;
FActive: Boolean;
FAddress: TAddress;
[JSONMarshalled(False)]
FInternalNote: string;
[JSONName('vat_number')]
FVatNumber: string;
public
constructor Create;
destructor Destroy; override;
property Id: Integer read FId write FId;
property Name: string read FName write FName;
property Active: Boolean read FActive write FActive;
property Address: TAddress read FAddress write FAddress;
property InternalNote: string read FInternalNote write FInternalNote;
property VatNumber: string read FVatNumber write FVatNumber;
end;
// ...
LCustomer.Id := 42;
LCustomer.Name := 'Daniele Teti';
LCustomer.Active := True;
LCustomer.VatNumber := 'IT01234567890';
LCustomer.InternalNote := 'do not send this to the client';
LCustomer.Address.City := 'Roma';
LCustomer.Address.ZipCode := '00100';
Writeln(TJson.ObjectToJsonString(LCustomer));
Output:
{"id":42,"name":"Daniele Teti","active":true,"address":{"city":"Roma","zipCode":"00100"},"vat_number":"IT01234567890"}
Qui sono successe tre cose.
Il TAddress annidato è stato serializzato anche lui, senza che tu lo chiedessi. REST.Json percorre il grafo degli oggetti.
InternalNote nell’output non c’è. [JSONMarshalled(False)] è il modo per tenere un campo fuori dal documento, ed è l’attributo che vuoi su tutto quello che il client non deve vedere.
Le chiavi sono uscite come id, name, zipCode. REST.Json legge i campi privati, non le proprietà, toglie il prefisso F e mette in minuscolo la prima lettera. Così FZipCode diventa zipCode, camelCase, che ti piaccia o no. Quando dall’altra parte pretendono un nome diverso, [JSONName('vat_number')] te lo cambia un campo alla volta.
Dal JSON all’oggetto
const
JSON_TEXT =
'{"Id":7,"Name":"Anna Bianchi","Active":false,' +
'"vat_number":"IT09876543210",' +
'"Address":{"City":"Milano","ZipCode":"20100"}}';
var
LCustomer: TCustomer;
begin
LCustomer := TJson.JsonToObject<TCustomer>(JSON_TEXT);
try
Writeln(Format('Id=%d Name=%s Active=%s Vat=%s City=%s',
[LCustomer.Id, LCustomer.Name, BoolToStr(LCustomer.Active, True),
LCustomer.VatNumber, LCustomer.Address.City]));
finally
LCustomer.Free;
end;
end;
Id=7 Name=Anna Bianchi Active=False Vat=IT09876543210 City=Milano
Nota che l’input usava Id e Name con la maiuscola e ha funzionato lo stesso: in lettura le maiuscole non contano, in scrittura sì. L’oggetto torna indietro completo, indirizzo annidato compreso, e sta a te liberarlo.
I campi che il JSON non nomina restano come li ha lasciati il costruttore:
LCustomer := TJson.JsonToObject<TCustomer>('{"Name":"Only a name"}');
Id=0 Name=Only a name Active=False
Nessuna eccezione. Se un Id mancante vuol dire che a monte qualcosa non va, controllarlo tocca a te.
Quello che ti costa un pomeriggio
Dichiara quelle classi nel .dpr e TJson.JsonToObject fallisce:
EConversionError: Internal: Cannot instantiate type restjson.TCustomer
Al serializzatore serve la RTTI linkata per la classe, e un tipo dichiarato nel file di programma non ce l’ha. Sposta le dichiarazioni in una unit e lo stesso codice funziona. In uscita la serializzazione non protesta mai, quindi lo incontri solo al ritorno, di solito dopo che ti sei convinto che il JSON sia malformato.
Ce n’è un’altra, specifica delle versioni recenti: TJson.Format è deprecata in Delphi 13 Florence. Il compilatore ti dice cosa usare al suo posto:
W1000 Symbol 'Format' is deprecated: 'Use TJSONAncestor.Format instead'
Quindi il pretty printing di un oggetto adesso è:
LJson := TJson.ObjectToJsonObject(LCustomer);
try
Writeln(LJson.Format); // TJSONAncestor.Format
finally
LJson.Free;
end;
{
"id": 1,
"name": "Pretty",
"active": false,
"address": {
"city": "Napoli",
"zipCode": ""
},
"vat_number": ""
}
Dove REST.Json si ferma
Serializza una lista e trovi il limite:
LArray := TJSONArray.Create;
try
for LCustomer in LList do
LArray.AddElement(TJson.ObjectToJsonObject(LCustomer));
Writeln(LArray.ToJSON);
finally
LArray.Free;
end;
Funziona, ed è già un ciclo che hai scritto tu a mano. Nel verso opposto, da un array JSON a una TObjectList<TCustomer>, REST.Json non ha proprio niente da offrire: l’array lo analizzi tu e chiami JsonToObject elemento per elemento.
REST.Json è molto bravo con un oggetto alla volta, con i nomi che sceglie lui. Oltre quello ti serve un serializzatore fatto apposta.
Quando serve di più: i serializzatori di DelphiMVCFramework
DelphiMVCFramework ha un serializzatore che puoi usare da solo, senza un server e senza nemmeno un controller. È una unit e un’interfaccia.
Una lista, in una chiamata, in tutti e due i versi:
uses
MVCFramework.Serializer.Intf,
MVCFramework.Serializer.Commons,
MVCFramework.Serializer.JsonDataObjects;
var
LSer: IMVCSerializer;
begin
LSer := TMVCJsonDataObjectsSerializer.Create;
Writeln(LSer.SerializeCollection(LOrders));
[{"id":1,"description":"Order 1","placedat":"2026-09-01T10:30:00.000+02:00"},{"id":2,"description":"Order 2","placedat":"2026-09-02T10:30:00.000+02:00"},{"id":3,"description":"Order 3","placedat":"2026-09-03T10:30:00.000+02:00"}]
E al ritorno:
LOrders := TObjectList<TOrder>.Create(True);
try
LSer.DeserializeCollection(JSON_TEXT, LOrders, TOrder);
objects rebuilt: 2
id=10 desc=From JSON placed=01/09/2026 10:30:00
id=11 desc=Second one placed=02/09/2026 10:30:00
Due chiamate dove REST.Json ti dava due cicli. Nota anche che il TDateTime è uscito ed è tornato come data vera, in ISO 8601 con l’offset, che è l’argomento della prossima sezione.
Il name case qui è una decisione, non una regola che ti viene imposta. Metti l’attributo sulla classe e tutta la classe lo segue:
[MVCNameCase(ncSnakeCase)]
TSnakeOrder = class
// ...
end;
[MVCNameCase(ncPascalCase)]
TPascalOrder = class
// ...
end;
ncSnakeCase : {"order_id":42,"customer_name":"Daniele Teti"}
ncPascalCase: {"OrderId":42,"CustomerName":"Daniele Teti"}
È il motivo per cui quasi tutti finiscono su questo serializzatore. Se l’API con cui devi parlare vuole order_id, REST.Json ti dà un [JSONName] per campo, per sempre; il serializzatore di DMVCFramework ti dà un attributo per classe.
Dentro però c’è una trappola, ed è silenziosa. Il name case vale anche in lettura. Il serializzatore usa ncLowerCase per default, quindi emette placedat e si aspetta placedat. Dagli lo stesso payload con placedAt e:
key written as "placedAt", serializer expects "placedat":
id=10 desc=From JSON placed=30/12/1899
no exception, the date is simply gone
30/12/1899 è lo zero di TDateTime. Nessun errore, nessun warning, solo un campo che non è arrivato, in silenzio. Quando consumi un’API che non controlli, allinea il name case al suo e prova un payload da un capo all’altro prima di credere a quello che vedi.
Vale anche qui la stessa regola sulla RTTI, tra l’altro. Dichiara TOrder nel .dpr e ottieni:
Exception: Cannot find RTTI for dmvcser.TOrder. Hint: Is the specified classtype linked in the module?
Serializzatore diverso, messaggio diverso, stessa causa: i tipi vanno nelle unit.
Le date in JSON, e l’ora che ci perderai
JSON non ha un tipo data. Qualunque cosa tu faccia, un TDateTime esce dal processo come stringa, e su quale stringa usare si sono messi d’accordo tutti: ISO 8601. Delphi ti dà la conversione in System.DateUtils, e ti dà un default sbagliato per la maggior parte del codice che stai scrivendo.
Il default è UTC
uses
System.DateUtils;
const
FIXED: TDateTime = 45000.5; // 2023-03-15 12:00:00
Writeln('local value : ', DateTimeToStr(FIXED));
Writeln('DateToISO8601(v) : ', DateToISO8601(FIXED));
Writeln('DateToISO8601(v,F) : ', DateToISO8601(FIXED, False));
local value : 15/03/2023 12:00:00
DateToISO8601(v) : 2023-03-15T12:00:00.000Z
DateToISO8601(v,F) : 2023-03-15T12:00:00.000+01:00
Il secondo parametro è AInputIsUTC e per default vale True. Quindi DateToISO8601(SomeDate) dice al mondo che il valore che gli hai passato è già UTC. Se arriva da Now, da un TDateTimePicker o da una colonna di database scritta da un’applicazione locale, UTC non è, e hai appena stampato una Z sopra un’ora locale.
Non solleva niente e il documento è valido. È l’ora a essere sbagliata.
Scrivi e leggi con lo stesso flag
LText := DateToISO8601(LOriginal, False);
LBack := ISO8601ToDate(LText, False);
Writeln('written : ', LText);
Writeln('read back: ', DateTimeToStr(LBack));
Writeln('identical: ', BoolToStr(SameDateTime(LOriginal, LBack), True));
written : 2023-03-15T12:00:00.000+01:00
read back: 15/03/2023 12:00:00
identical: True
Mescola i flag e il valore si sposta del tuo offset da UTC, in silenzio:
LText := DateToISO8601(LOriginal, False); // locale
LBack := ISO8601ToDate(LText); // default, lo tratta come UTC
written with False, read with the default:
15/03/2023 12:00:00 -> 15/03/2023 11:00:00
drift in minutes: 60
Un’ora, su una macchina in Italia a marzo. Ad agosto sono due. Su una macchina in UTC sono zero, ed è esattamente per questo che sopravvive ai test e salta fuori da un cliente.
Dentro un documento
LJson := TJSONObject.Create;
try
LJson.AddPair('event', 'invoice.created');
LJson.AddPair('created_at', DateToISO8601(FIXED, False));
Writeln(LJson.ToJSON);
LWhen := ISO8601ToDate(LJson.GetValue<string>('created_at'), False);
Writeln('parsed back: ', DateTimeToStr(LWhen));
finally
LJson.Free;
end;
{"event":"invoice.created","created_at":"2023-03-15T12:00:00.000+01:00"}
parsed back: 15/03/2023 12:00:00
Input che non hai scritto tu
ISO8601ToDate solleva un’eccezione su tutto quello che non riesce a leggere. Per un payload arrivato dalla rete, usa la versione Try:
if TryISO8601ToDate('2026-13-45T99:00:00', LWhen, False) then
Writeln('parsed: ', DateTimeToStr(LWhen))
else
Writeln('TryISO8601ToDate returned False, no exception raised');
TryISO8601ToDate returned False, no exception raised
Stessa forma di TryGetValue più su in questo articolo, e stesso motivo per preferirla.
Scegli UTC o locale una volta sola, per tutta l’applicazione, e passa il flag esplicitamente ogni singola volta. Il default non sarà quello che intendevi tu.
Esempio Pratico: Client REST API
Ecco uno scenario vero che mette insieme i pezzi visti finora: chiami una REST API ed elabori la risposta JSON. L’esempio si connette a JSONPlaceholder, un’API di test gratuita, prende una lista di utenti e la traduce in record Delphi. Nota che TryGetValue è ovunque: con un’API esterna che può cambiare da un giorno all’altro, è quello che ti salva quando un campo sparisce.
THTTPClient richiede Delphi XE8 o successivo.
program JSONRestApiClient;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON,
System.Net.HttpClient; // Richiede Delphi XE8+
type
TUser = record
ID: Integer;
Name: string;
Email: string;
Username: string;
end;
function ParseUser(AJSONObject: TJSONObject): TUser;
begin
// Uso di TryGetValue per sicurezza
if not AJSONObject.TryGetValue<Integer>('id', Result.ID) then
Result.ID := 0;
if not AJSONObject.TryGetValue<string>('name', Result.Name) then
Result.Name := '';
if not AJSONObject.TryGetValue<string>('email', Result.Email) then
Result.Email := '';
if not AJSONObject.TryGetValue<string>('username', Result.Username) then
Result.Username := '';
end;
var
LClient: THTTPClient;
LResponse: IHTTPResponse;
LJSONValue: TJSONValue;
LJSONArray: TJSONArray;
LUserJSON: TJSONObject;
LUser: TUser;
I: Integer;
begin
WriteLn('Recupero utenti dall''API JSONPlaceholder...');
WriteLn;
LClient := THTTPClient.Create;
try
LResponse := LClient.Get('https://jsonplaceholder.typicode.com/users');
if LResponse.StatusCode <> 200 then
begin
WriteLn('Errore HTTP: ', LResponse.StatusCode);
ReadLn;
Exit;
end;
// Analizza la risposta array JSON
LJSONValue := TJSONObject.ParseJSONValue(LResponse.ContentAsString);
if LJSONValue = nil then
begin
WriteLn('Risposta JSON non valida');
ReadLn;
Exit;
end;
try
if not (LJSONValue is TJSONArray) then
begin
WriteLn('Atteso array JSON');
Exit;
end;
LJSONArray := LJSONValue as TJSONArray;
WriteLn('Trovati ', LJSONArray.Count, ' utenti:');
WriteLn(StringOfChar('-', 50));
for I := 0 to LJSONArray.Count - 1 do
begin
LUserJSON := LJSONArray.Items[I] as TJSONObject;
LUser := ParseUser(LUserJSON);
WriteLn('ID: ', LUser.ID);
WriteLn('Nome: ', LUser.Name);
WriteLn('Email: ', LUser.Email);
WriteLn('Username: ', LUser.Username);
WriteLn(StringOfChar('-', 50));
end;
finally
LJSONValue.Free;
end;
finally
LClient.Free;
end;
ReadLn;
end.
Esempio Pratico: Gestore File di Configurazione
Ultimo esempio: una classe completa per gestire la configurazione. TConfigManager incapsula caricamento, salvataggio e accesso alle impostazioni dietro un’API pulita e type-safe. Carica il file solo quando serve, usa valori predefiniti per le chiavi mancanti, e crea il file da solo se manca. Prendilo come punto di partenza per la tua configurazione:
program JSONConfigManager;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.IOUtils,
System.JSON;
type
TConfigManager = class
private
FFileName: string;
FJSONObject: TJSONObject;
FModified: Boolean;
procedure EnsureLoaded;
public
constructor Create(const AFileName: string);
destructor Destroy; override;
procedure Load;
procedure Save;
function GetString(const AKey: string; const ADefault: string = ''): string;
function GetInteger(const AKey: string; const ADefault: Integer = 0): Integer;
function GetBoolean(const AKey: string; const ADefault: Boolean = False): Boolean;
procedure SetValue(const AKey: string; const AValue: string); overload;
procedure SetValue(const AKey: string; const AValue: Integer); overload;
procedure SetValue(const AKey: string; const AValue: Boolean); overload;
property FileName: string read FFileName;
property Modified: Boolean read FModified;
end;
constructor TConfigManager.Create(const AFileName: string);
begin
inherited Create;
FFileName := AFileName;
FJSONObject := nil;
FModified := False;
end;
destructor TConfigManager.Destroy;
begin
FJSONObject.Free;
inherited;
end;
procedure TConfigManager.EnsureLoaded;
begin
if FJSONObject = nil then
Load;
end;
procedure TConfigManager.Load;
var
LContent: string;
LJSONValue: TJSONValue;
begin
FreeAndNil(FJSONObject);
FModified := False;
if TFile.Exists(FFileName) then
begin
LContent := TFile.ReadAllText(FFileName);
LJSONValue := TJSONObject.ParseJSONValue(LContent);
if (LJSONValue <> nil) and (LJSONValue is TJSONObject) then
FJSONObject := TJSONObject(LJSONValue)
else if LJSONValue <> nil then
LJSONValue.Free;
end;
if FJSONObject = nil then
FJSONObject := TJSONObject.Create;
end;
procedure TConfigManager.Save;
begin
EnsureLoaded;
{$IF CompilerVersion >= 33.0}
TFile.WriteAllText(FFileName, FJSONObject.Format());
{$ELSE}
TFile.WriteAllText(FFileName, FJSONObject.ToString);
{$ENDIF}
FModified := False;
end;
function TConfigManager.GetString(const AKey, ADefault: string): string;
begin
EnsureLoaded;
if not FJSONObject.TryGetValue<string>(AKey, Result) then
Result := ADefault;
end;
function TConfigManager.GetInteger(const AKey: string; const ADefault: Integer): Integer;
begin
EnsureLoaded;
if not FJSONObject.TryGetValue<Integer>(AKey, Result) then
Result := ADefault;
end;
function TConfigManager.GetBoolean(const AKey: string; const ADefault: Boolean): Boolean;
begin
EnsureLoaded;
if not FJSONObject.TryGetValue<Boolean>(AKey, Result) then
Result := ADefault;
end;
procedure TConfigManager.SetValue(const AKey: string; const AValue: string);
begin
EnsureLoaded;
FJSONObject.RemovePair(AKey).Free;
FJSONObject.AddPair(AKey, AValue);
FModified := True;
end;
procedure TConfigManager.SetValue(const AKey: string; const AValue: Integer);
begin
EnsureLoaded;
FJSONObject.RemovePair(AKey).Free;
FJSONObject.AddPair(AKey, AValue);
FModified := True;
end;
procedure TConfigManager.SetValue(const AKey: string; const AValue: Boolean);
begin
EnsureLoaded;
FJSONObject.RemovePair(AKey).Free;
FJSONObject.AddPair(AKey, AValue);
FModified := True;
end;
// Dimostrazione di utilizzo
var
Config: TConfigManager;
LConfigFile: string;
begin
LConfigFile := TPath.Combine(TPath.GetDocumentsPath, 'appsettings.json');
WriteLn('File di configurazione: ', LConfigFile);
WriteLn;
Config := TConfigManager.Create(LConfigFile);
try
// Imposta alcuni valori (le chiavi sono piatte - non oggetti annidati)
Config.SetValue('databaseHost', 'localhost');
Config.SetValue('databasePort', 5432);
Config.SetValue('databaseName', 'myapp');
Config.SetValue('loggingEnabled', True);
Config.SetValue('loggingMaxFiles', 10);
Config.Save;
WriteLn('Configurazione salvata!');
WriteLn;
// Rileggi i valori
WriteLn('Host Database: ', Config.GetString('databaseHost'));
WriteLn('Porta Database: ', Config.GetInteger('databasePort'));
WriteLn('Logging Abilitato: ', Config.GetBoolean('loggingEnabled'));
// Leggi con valore predefinito
WriteLn('Timeout (predefinito 30): ', Config.GetInteger('timeout', 30));
finally
Config.Free;
end;
ReadLn;
end.
Output:
File di configurazione: C:\Users\yourname\Documents\appsettings.json
Configurazione salvata!
Host Database: localhost
Porta Database: 5432
Logging Abilitato: TRUE
Timeout (predefinito 30): 30
Librerie JSON di Terze Parti
Il parser integrato copre quasi tutto. In alcuni casi però conviene una libreria di terze parti:
| Libreria | Migliore Per | URL |
|---|---|---|
| JsonDataObjects | Alte prestazioni, usata da DelphiMVCFramework | GitHub |
| Grijjy Foundation | Ricca di funzionalità, include supporto BSON | GitHub |
| mORMot2 | Framework full-stack (ORM, SOA, REST) che si porta dietro il suo strato JSON | GitHub |
Quando Usare Librerie di Terze Parti
- File JSON grandi (>10MB): Considera parser streaming o JsonDataObjects
- Parsing ad alta frequenza: JsonDataObjects, confrontato con
System.JSONpiù avanti in questo articolo - Necessità supporto BSON: Grijjy Foundation
- Serializzazione oggetti:
REST.Jsonper i casi semplici, i serializzatori di DelphiMVCFramework per liste, dataset e controllo del name case
Per la maggior parte delle applicazioni System.JSON integrato basta, e in più non porta dipendenze esterne.
Quanto è veloce davvero System.JSON
“Usa una libreria di terze parti se ti servono le prestazioni” è facile da scrivere e difficile da mettere in pratica. Ecco i numeri.
Il test costruisce un array di 50.000 record, ognuno con un intero, due stringhe, un booleano, un numero e un timestamp ISO 8601: 14 MB di testo UTF-16, la forma di un export vero. Poi misura due lavori: analizzare e leggere un intero da ogni record, che è quello che fa un client, e analizzare e riserializzare subito in uscita, che è quello che fa un proxy.
Ogni misura è preceduta da un warm-up che viene scartato, e quella riportata è la migliore di sette, quindi i numeri sono il minimo, non la media di qualunque altra cosa stesse facendo la macchina.
payload: 50000 records, 14657 KB of UTF-16 text
best of 7 runs, one warm-up discarded
System.JSON parse + read ids 158 ms 90,6 MB/s
JsonDataObjects parse + read ids 42 ms 340,8 MB/s
System.JSON parse + serialize 170 ms 84,2 MB/s
JsonDataObjects parse + serialize 63 ms 227,2 MB/s
Delphi 13 Florence, Win32, ottimizzazione attiva, controlli di range e overflow disattivati, su un Core i9-13980HX con Windows 11.
Quindi: JsonDataObjects analizza circa quattro volte più in fretta, e fa il giro completo circa due volte e mezzo più in fretta. Il divario è reale ed è stabile da un’esecuzione all’altra.
System.JSON ha comunque masticato 14 MB in circa un sesto di secondo. Se il tuo JSON è di qualche centinaio di kilobyte, che copre quasi tutte le risposte REST e praticamente ogni file di configurazione, stai scegliendo tra due millisecondi e mezzo millisecondo. Non è una decisione, è un errore di arrotondamento, e System.JSON è già installato.
Passa a JsonDataObjects quando il payload si misura in megabyte, o quando analizzi dentro un ciclo che gira migliaia di volte, o quando sei su un dispositivo dove la CPU non è gratis. Altrimenti la dipendenza ti costa più di quanto ti dia.
Una nota pratica se lo porti dentro: JsonDataObjects dichiara i propri TJSONObject e TJSONArray. In una unit che le usa tutte e due vince quella che viene per ultima nella clausola uses, e ti prendi errori che non hanno senso finché non te ne accorgi:
E2003 Undeclared identifier: 'ParseJSONValue'
E2010 Incompatible types: 'System.JSON.TJSONValue' and 'JsonDataObjects.TJsonArray'
Qualifica i nomi dei tipi, System.JSON.TJSONObject e JsonDataObjects.TJsonArray, e l’ambiguità sparisce.
mORMot2 in questo confronto non c’è. È un framework intero più che una libreria JSON, e misurarlo in modo onesto vuol dire tirarselo dentro e configurarlo tutto, che è un altro articolo.
Costruire REST API con JSON
Se costruisci REST API in Delphi, DelphiMVCFramework ti dà serializzazione JSON automatica:
[MVCPath('/api/customers')]
TCustomersController = class(TMVCController)
public
[MVCPath]
[MVCHTTPMethod([httpGET])]
procedure GetCustomers;
[MVCPath('/($id)')]
[MVCHTTPMethod([httpGET])]
procedure GetCustomer(id: Integer);
end;
procedure TCustomersController.GetCustomers;
var
LCustomers: TObjectList<TCustomer>;
begin
LCustomers := TCustomerService.GetAll;
Render(LCustomers); // Serializzazione JSON automatica
end;
Vedi i sample di DelphiMVCFramework per esempi completi, e la guida ufficiale se preferisci che i serializzatori te li spieghino invece di indovinarli.
Domande Frequenti (FAQ)
Come faccio a fare il parsing di una stringa JSON in Delphi?
Usa TJSONObject.ParseJSONValue() dall’unit System.JSON:
uses System.JSON;
var
LJSONObject: TJSONObject;
LValue: TJSONValue;
begin
LValue := TJSONObject.ParseJSONValue('{"name":"John"}');
if (LValue <> nil) and (LValue is TJSONObject) then
begin
LJSONObject := TJSONObject(LValue);
try
WriteLn(LJSONObject.GetValue<string>('name')); // Output: John
finally
LJSONObject.Free;
end;
end;
end;
Come gestisco i valori null in JSON?
Usa TryGetValue per gestire in sicurezza valori mancanti o null:
var
LValue: string;
begin
if LJSONObject.TryGetValue<string>('optionalField', LValue) then
WriteLn('Valore: ', LValue)
else
WriteLn('Campo mancante o null');
end;
Come itero su un array JSON?
Usa la sintassi moderna del ciclo for-in:
var
LArray: TJSONArray;
LItem: TJSONValue;
begin
if LJSONObject.TryGetValue<TJSONArray>('items', LArray) then
begin
for LItem in LArray do
WriteLn(LItem.Value);
end;
end;
Qual è la differenza tra Format() e ToString()?
Format(): Restituisce JSON indentato e leggibile (Solo Delphi 10.3+)ToString(): Restituisce JSON compatto senza spazi (meglio per il trasferimento di rete, funziona in tutte le versioni)
Come modifico un oggetto JSON esistente?
Usa RemovePair poi AddPair. RemovePair restituisce la coppia rimossa (o nil se non trovata) - ne hai la proprietà e devi liberarla:
begin
// Remove restituisce la coppia - devi liberarla!
// Free è sicuro da chiamare su nil (controlla internamente Self <> nil)
LJSONObject.RemovePair('name').Free;
// Aggiungi nuovo valore
LJSONObject.AddPair('name', 'Nuovo Valore');
end;
Quale versione di Delphi ha introdotto il supporto JSON?
- Delphi 2009: Supporto JSON iniziale nell’unit
DBXJSON - Delphi XE6: Rinominata in
System.JSONcon miglioramenti API - Delphi 10.1 Berlin: API fluent
TJSONObjectBuilder - Delphi 10.3 Rio: Aggiunto metodo
Format(),EJSONParseExceptioncon informazioni dettagliate sull’errore
Qual è la differenza tra GetValue, FindValue e TryGetValue?
| Metodo | Restituisce | Se Chiave Non Trovata |
|---|---|---|
GetValue<T>('key') |
Valore di tipo T | Solleva eccezione |
FindValue('key') |
TJSONValue o nil | Restituisce nil |
TryGetValue<T>('key', outVar) |
Boolean | Restituisce False |
Raccomandazione: usa TryGetValue in produzione. È l’approccio più sicuro.
Come creo una copia profonda di un oggetto JSON?
Usa il metodo Clone:
var
LOriginal, LCopy: TJSONObject;
begin
LOriginal := TJSONObject.ParseJSONValue('{"name":"test"}') as TJSONObject;
try
LCopy := LOriginal.Clone as TJSONObject;
try
// LCopy è indipendente - le modifiche non influenzano LOriginal
finally
LCopy.Free;
end;
finally
LOriginal.Free;
end;
end;
Come controllo se un valore JSON è null?
var
LValue: TJSONValue;
begin
LValue := LJSONObject.FindValue('myField');
if LValue = nil then
WriteLn('Il campo non esiste')
else if LValue is TJSONNull then
WriteLn('Il campo esiste ma è null')
else
WriteLn('Il campo ha un valore: ', LValue.Value);
end;
Posso usare la notazione a percorso per accedere agli elementi di un array?
Sì, usa la notazione tra parentesi quadre con l’indice:
var
LFirstSkill: string;
begin
// Accedi al primo elemento dell'array skills
if LJSONObject.TryGetValue<string>('skills[0]', LFirstSkill) then
WriteLn('Prima competenza: ', LFirstSkill);
end;
Come converto un oggetto Delphi in JSON?
Con TJson.ObjectToJsonString di REST.Json, che Delphi include già. Percorre il grafo degli oggetti, legge i campi privati e ti dà chiavi camelCase; [JSONName] rinomina un campo e [JSONMarshalled(False)] ne tiene uno fuori. Per liste, controllo del name case e dataset, usa i serializzatori di DelphiMVCFramework. Vedi Da oggetti a JSON con REST.Json più su.
System.JSON è abbastanza veloce?
Per quasi tutto, sì. Su 14 MB di JSON, 50.000 record, System.JSON analizza e legge in circa 158 ms; JsonDataObjects fa lo stesso lavoro in 42 ms, circa quattro volte più in fretta. Su un payload di qualche centinaio di kilobyte, che copre quasi tutte le risposte REST e ogni file di configurazione, la differenza è una frazione di millisecondo. Cambia libreria quando i documenti si misurano in megabyte o quando analizzi dentro un ciclo stretto, non per default. I numeri e il metodo sono in Quanto è veloce davvero System.JSON.
Come serializzo una TObjectList in JSON?
REST.Json non ha supporto per le liste: fai un ciclo, chiami TJson.ObjectToJsonObject per ogni elemento e aggiungi ognuno a un TJSONArray. Nel verso opposto non ha proprio niente, quindi l’array lo analizzi tu e chiami JsonToObject elemento per elemento. Il serializzatore di DelphiMVCFramework fa tutte e due le cose in una chiamata, SerializeCollection e DeserializeCollection.
Perché TJson.JsonToObject solleva “Cannot instantiate type”?
Perché la classe è dichiarata nel file di programma .dpr, e lì la RTTI non viene linkata. Sposta la dichiarazione del tipo in una unit e lo stesso codice funziona. In uscita la serializzazione non protesta mai, quindi l’errore compare solo al ritorno. Il serializzatore di DelphiMVCFramework fallisce per la stessa causa con un messaggio diverso, Cannot find RTTI for ....
System.JSON è thread-safe?
No. TJSONObject e le classi collegate non sono thread-safe. Se più thread toccano lo stesso oggetto, la sincronizzazione (sezioni critiche, lock) la scrivi tu. In sola lettura, dopo il parsing, puoi condividerlo tra thread senza problemi, finché nessuno lo modifica.
Come serializzo un TDateTime in JSON?
TJSONObject non ha un overload di AddPair per TDateTime. Converti prima in una stringa ISO 8601, e passa AInputIsUTC esplicitamente, perché per default vale True ed etichetta un’ora locale come UTC. Vedi Le date in JSON, e l’ora che ci perderai:
LJSONObject.AddPair('createdAt', FormatDateTime('yyyy-mm-dd"T"hh:nn:ss', Now));
Qual è la dimensione massima di JSON che Delphi può analizzare?
Non c’è un limite rigido, ma System.JSON carica l’intero documento in memoria. Per file molto grandi (>100MB), considera parser streaming come TJsonTextReader da System.JSON.Readers, o librerie di terze parti ottimizzate per documenti grandi.
Qual è la differenza tra System.JSON e DBXJSON?
Stessa libreria, solo rinominata. DBXJSON era il nome originale, da Delphi 2009 a XE5; da XE6 si chiama System.JSON, per seguire le nuove convenzioni. L’API è praticamente identica, quindi migrare vecchio codice è banale.
Come faccio a formattare in modo leggibile (pretty print) JSON in Delphi?
Usa il metodo Format() (Delphi 10.3+) che restituisce JSON indentato e leggibile:
WriteLn(LJSONObject.Format()); // Formattato con indentazione
WriteLn(LJSONObject.ToString); // Compatto, singola riga
Per le versioni più vecchie di Delphi, usa librerie di terze parti o implementa una formattazione personalizzata.
Come gestisco i caratteri speciali e Unicode in JSON?
System.JSON gestisce Unicode e l’escape dei caratteri speciali da solo, quando genera JSON. In lettura, converte \n, \t, \uXXXX senza che tu debba fare niente:
LJSONObject.AddPair('message', 'Riga 1'#13#10'Riga 2'); // Newline con escape automatico
LJSONObject.AddPair('emoji', '🚀'); // Unicode funziona direttamente
Come unisco due oggetti JSON?
Non c’è una funzione di merge integrata. Itera su un oggetto e aggiungi le sue coppie all’altro:
for LPair in LSource do
LTarget.AddPair(LPair.JsonString.Value, LPair.JsonValue.Clone as TJSONValue);
Nota: clona i valori, perché possono appartenere a un solo oggetto genitore.
Come valido JSON prima del parsing?
ParseJSONValue restituisce nil su JSON non valido: è già una validazione di base. Per validare uno schema (struttura, campi obbligatori, tipi) ti serve una libreria di terze parti: Delphi non ha JSON Schema integrato.
Come accedo ad array profondamente annidati?
Combina la notazione a percorso con l’indicizzazione dell’array:
// Accedi a: {"data": {"users": [{"name": "Alice"}, {"name": "Bob"}]}}
if LJSONObject.TryGetValue<string>('data.users[1].name', LValue) then
WriteLn(LValue); // Output: Bob
Posso usare JSON con i dataset FireDAC?
Sì, ma non c’è integrazione diretta. O iteri il dataset a mano e costruisci il JSON, o usi una libreria di serializzazione: sia DelphiMVCFramework che mORMot2 hanno il dataset-to-JSON già pronto.
Come gestisco JSON con chiavi duplicate?
JSON tecnicamente permette chiavi duplicate, anche se è una pessima idea. TJSONObject le memorizza tutte, ma GetValue/TryGetValue restituiscono solo la prima corrispondenza. Per prenderle tutte, itera con un for-in.
Riepilogo
Delphi ha un supporto JSON solido, integrato nell’unit System.JSON:
- Usa
TJSONObjecteTJSONArrayper creare e analizzare JSON - Controlla sempre nil quando analizzi stringhe JSON
- Usa
TryGetValueper la lettura sicura dei valori con campi opzionali - Usa la notazione a percorso (
'parent.child') per valori annidati - Ricorda la gestione della memoria: gli oggetti genitori possiedono i loro figli;
RemovePairrestituisce la proprietà a te - Considera librerie di terze parti solo per esigenze specifiche di prestazioni
- Usa
Format()per output leggibile (Delphi 10.3+),ToString()per output compatto - Usa cicli for-in per iterazione più pulita su array e coppie di oggetti
Per REST API moderne in Delphi, dai un’occhiata a DelphiMVCFramework: serializzazione JSON avanzata, in produzione da anni in progetti reali.
Comments
comments powered by Disqus