JSON en Delphi : le guide complet avec exemples (2026)
🇬🇧 English • 🇮🇹 Italiano • 🇪🇸 Español • 🇩🇪 Deutsch
JSON (JavaScript Object Notation) est le standard de fait pour l’échange de données entre applications. Tu écris une API REST, tu lis un fichier de configuration, tu discutes avec un service web : dans les trois cas, il faut savoir manipuler du JSON en Delphi.
Ce guide couvre le support JSON de Delphi de bout en bout, avec des exemples complets et compilables, prêts à coller dans tes projets.
TJSONObject, TJSONArray). Pour le parsing en streaming (style SAX), regarde les unités System.JSON.Readers et System.JSON.Writers.
Compatibilité entre versions de Delphi
Le support JSON a beaucoup bougé d’une version de Delphi à l’autre :
| Version | Unité | Nouveautés |
|---|---|---|
| Delphi 2009 | DBXJSON |
Premier support JSON, avec les classes de base |
| Delphi XE6 | System.JSON |
Unité renommée, API améliorée |
| Delphi 10.1 Berlin | System.JSON |
API fluide TJSONObjectBuilder, TryGetValue<T> amélioré |
| Delphi 10.3 Rio | System.JSON |
Méthode Format(), EJSONParseException avec les détails, gains de performance |
| Delphi 11-12 | System.JSON |
Optimisations et finitions supplémentaires |
| Delphi 13 Florence | System.JSON |
Dernières améliorations, support poursuivi |
C’est quoi, JSON ?
JSON est un format d’échange de données textuel et léger. Un humain le lit et l’écrit sans effort, une machine le parse et le génère aussi facilement. Un document JSON peut contenir :
- Des objets : des paires clé-valeur entre accolades
{} - Des tableaux : des listes ordonnées de valeurs entre crochets
[] - Des valeurs : chaînes, nombres, booléens (
true/false),null, objets ou tableaux
Exemple de structure JSON :
{
"name": "Daniele Teti",
"age": 45,
"active": true,
"skills": ["Delphi", "Python", "SQL"],
"address": {
"city": "Rome",
"country": "Italy"
}
}
Les classes JSON de Delphi
Delphi embarque son support JSON dans l’unité System.JSON. Les classes principales sont celles-ci :
| Classe | Description |
|---|---|
TJSONValue |
Classe de base de tous les types de valeur JSON |
TJSONObject |
Représente un objet JSON (paires clé-valeur) |
TJSONArray |
Représente un tableau JSON (liste ordonnée) |
TJSONString |
Représente une valeur chaîne |
TJSONNumber |
Représente une valeur numérique |
TJSONBool |
Représente une valeur booléenne |
TJSONNull |
Représente la valeur null |
TJSONPair |
Représente une paire clé-valeur dans un objet |
Créer des objets JSON
Commence par la base : créer des objets JSON et leur ajouter des propriétés.
Création d’un objet JSON
L’opération de départ, c’est créer un TJSONObject et y ajouter des paires clé-valeur. Delphi fournit des surcharges de AddPair qui acceptent directement des chaînes, des entiers, des booléens et des doubles : pas besoin d’envelopper les valeurs primitives dans des classes JSON dédiées. Un objet avec des informations personnelles et plusieurs types de données :
program JSONCreateBasic;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LJSONObject: TJSONObject;
begin
LJSONObject := TJSONObject.Create;
try
// Ajoute une propriété chaîne
LJSONObject.AddPair('firstName', 'Daniele');
LJSONObject.AddPair('lastName', 'Teti');
// Ajoute une propriété numérique (surcharges Integer, Int64, Double disponibles)
LJSONObject.AddPair('age', 45);
// Ajoute une propriété booléenne
LJSONObject.AddPair('active', True);
// Ajoute une propriété null (pas de surcharge, il faut passer par TJSONNull)
LJSONObject.AddPair('middleName', TJSONNull.Create);
// Affiche le JSON
// Note : Format() est disponible depuis 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.
Sortie :
{
"firstName": "Daniele",
"lastName": "Teti",
"age": 45,
"active": true,
"middleName": null
}
Créer des tableaux JSON
Un tableau JSON est une collection ordonnée qui accepte n’importe quelle combinaison de valeurs : chaînes, nombres, booléens, et même d’autres tableaux ou objets. Quand tu ajoutes un TJSONArray à un TJSONObject avec AddPair, l’objet parent devient propriétaire du tableau : tu ne libères que l’objet racine. Un tableau de chaînes, puis un tableau aux types mélangés :
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');
// Crée un tableau de chaînes
LSkills := TJSONArray.Create;
LJSONObject.AddPair('skills', LSkills);
LSkills.Add('Delphi');
LSkills.Add('Python');
LSkills.Add('SQL');
// Crée un tableau avec des types mixtes
LContacts := TJSONArray.Create;
LJSONObject.AddPair('contacts', LContacts);
LContacts.Add('daniele@example.com'); // chaîne
LContacts.Add(123456); // nombre
LContacts.Add(True); // booléen
{$IF CompilerVersion >= 33.0}
WriteLn(LJSONObject.Format());
{$ELSE}
WriteLn(LJSONObject.ToString);
{$ENDIF}
finally
LJSONObject.Free; // Libère aussi LContacts et LSkills
end;
ReadLn;
end.
Sortie :
{
"name": "Daniele Teti",
"skills": [
"Delphi",
"Python",
"SQL"
],
"contacts": [
"daniele@example.com",
123456,
true
]
}
Créer un tableau d’objets
Un des motifs les plus courants dans le JSON de la vraie vie, c’est un tableau qui contient plusieurs objets : une liste d’utilisateurs, de produits, ou n’importe quelle collection d’enregistrements. Chaque objet du tableau a ses propres propriétés. Tu crées chaque objet séparément et tu l’ajoutes au tableau avec la méthode Add. Le tableau devient alors propriétaire de tout ce que tu lui ajoutes :
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);
// Premier utilisateur
LUser := TJSONObject.Create;
LUsers.Add(LUser);
LUser.AddPair('id', 1);
LUser.AddPair('name', 'Alice');
LUser.AddPair('email', 'alice@example.com');
// Deuxième utilisateur
LUser := TJSONObject.Create;
LUsers.Add(LUser);
LUser.AddPair('id', 2);
LUser.AddPair('name', 'Bob');
LUser.AddPair('email', 'bob@example.com');
// Troisième utilisateur
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.
Sortie :
{
"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"
}
]
}
Objets JSON imbriqués
Dès que les données sont un peu complexes, il leur faut une hiérarchie : une personne a une adresse, l’adresse a une ville et un pays, et ainsi de suite. En Delphi, tu construis ces structures en ajoutant des instances de TJSONObject comme valeurs d’autres objets. Comme pour les tableaux, l’objet parent devient propriétaire de ses enfants, ce qui simplifie la gestion mémoire. Une personne avec une adresse et une société imbriquées :
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');
// Crée l'objet adresse imbriqué
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');
// Crée un autre objet imbriqué
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.
Sortie :
{
"name": "Daniele Teti",
"address": {
"street": "Via Roma 123",
"city": "Rome",
"country": "Italy",
"zipCode": "00100"
},
"company": {
"name": "bit Time Professionals",
"website": "https://www.bittime.it"
}
}
Utiliser TJSONObjectBuilder (Delphi 10.1 Berlin+)
Si tu préfères une syntaxe déclarative et chaînable pour construire ton JSON, Delphi 10.1 Berlin a introduit TJSONObjectBuilder. Cette API fluide te permet d’écrire une structure complexe en une seule expression, en enchaînant BeginObject, BeginArray, Add et EndObject/EndArray. Le builder écrit dans un TJsonTextWriter, qui écrit lui-même dans un TStringBuilder. La mise en place est plus verbeuse, mais sur les structures compliquées le code se lit mieux :
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
// Construit le JSON avec l'API fluide
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.
Sortie :
{
"firstName": "Daniele",
"lastName": "Teti",
"age": 45,
"active": true,
"address": {
"city": "Rome",
"country": "Italy"
},
"skills": [
"Delphi",
"Python",
"SQL"
]
}
Parser une chaîne JSON
Quand tu reçois du JSON d’un service web, d’un fichier ou d’ailleurs, il faut le transformer en objets Delphi utilisables. C’est le travail de la méthode de classe TJSONObject.ParseJSONValue. Elle renvoie un TJSONValue (la classe de base), tu dois donc vérifier que c’est bien le type attendu, en général TJSONObject ou TJSONArray. Si le JSON est mal formé, la méthode renvoie nil : vérifie-le toujours avant d’aller plus loin.
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 renvoie un TJSONValue, transtype vers le type attendu
LJSONValue := TJSONObject.ParseJSONValue(JSON_STRING);
if LJSONValue = nil then
begin
WriteLn('ERROR: Invalid JSON!');
ReadLn;
Exit;
end;
try
// Vérifie que c'est un objet (à la racine, ça pourrait être un tableau)
if not (LJSONValue is TJSONObject) then
begin
WriteLn('ERROR: Expected JSON object at root level');
Exit;
end;
LJSONObject := TJSONObject(LJSONValue); // Transtypage direct, sûr après le test "is"
WriteLn('Parsed successfully!');
WriteLn('Number of pairs: ', LJSONObject.Count);
{$IF CompilerVersion >= 33.0}
WriteLn(LJSONObject.Format());
{$ELSE}
WriteLn(LJSONObject.ToString);
{$ENDIF}
finally
LJSONValue.Free;
end;
ReadLn;
end.
ParseJSONValue renvoie nil : ça veut dire que le JSON n'est pas valide.
Gérer les erreurs de parsing (Delphi 10.3+)
Quand le parsing échoue, savoir pourquoi change tout pour le débogage. Depuis Delphi 10.3 Rio, tu passes True en deuxième paramètre de ParseJSONValue et la méthode lève une EJSONParseException au lieu de renvoyer nil. L’exception porte le message, le chemin où le parsing a lâché et l’offset du caractère, trois informations qui servent vraiment quand le JSON est complexe ou fourni par quelqu’un d’autre :
program JSONParseErrors;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
INVALID_JSON = '{"name": "Test", "value": }'; // Invalide !
var
LJSONValue: TJSONValue;
begin
{$IF CompilerVersion >= 33.0} // Delphi 10.3 Rio
try
// L'option RaiseExc donne l'exception avec les détails
LJSONValue := TJSONObject.ParseJSONValue(INVALID_JSON, True);
try
WriteLn('Parsed: ', LJSONValue.ToString);
finally
LJSONValue.Free;
end;
except
on E: EJSONParseException do
begin
WriteLn('Parse error!');
WriteLn(' Message: ', E.Message);
WriteLn(' Path: ', E.Path);
WriteLn(' Offset: ', E.Offset);
end;
end;
{$ELSE}
// Avant 10.3 : impossible de faire mieux que tester nil
LJSONValue := TJSONObject.ParseJSONValue(INVALID_JSON);
if LJSONValue = nil then
WriteLn('Invalid JSON - no details available')
else
LJSONValue.Free;
{$ENDIF}
ReadLn;
end.
Lire des valeurs JSON
Delphi propose plusieurs façons de lire une valeur dans un objet JSON, chacune avec son compromis entre confort et sécurité. Choisis la bonne au bon moment, et une donnée manquante ou inattendue ne fait pas planter le programme.
Méthode 1 : GetValue avec type générique (Delphi XE7+)
La façon la plus simple de lire une valeur, c’est la méthode générique GetValue<T>. Tu indiques le type attendu en paramètre de type et Delphi fait la conversion. Attention : elle lève une exception si la clé n’existe pas, donc réserve-la aux cas où tu es sûr que la clé est là.
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> lève une exception si la clé n'est pas trouvée
WriteLn('Name: ', LJSONObject.GetValue<string>('name'));
WriteLn('Age: ', LJSONObject.GetValue<Integer>('age'));
WriteLn('Active: ', LJSONObject.GetValue<Boolean>('active'));
finally
LJSONObject.Free;
end;
ReadLn;
end.
Méthode 2 : TryGetValue, la lecture sûre (recommandée)
En production, TryGetValue<T> est la bonne approche. Elle renvoie False si la clé manque ou si la valeur ne se convertit pas dans le type demandé : tu traites la donnée absente sans passer par une exception. C’est particulièrement pratique sur du JSON venu de l’extérieur, où tu ne peux pas garantir que tous les champs sont là.
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 renvoie False si la clé n'est pas trouvée (aucune exception)
if LJSONObject.TryGetValue<string>('name', LName) then
WriteLn('Name: ', LName)
else
WriteLn('Name not found');
if LJSONObject.TryGetValue<Integer>('age', LAge) then
WriteLn('Age: ', LAge)
else
WriteLn('Age not found');
// Cette clé n'existe pas, aucune exception n'est levée
if LJSONObject.TryGetValue<string>('middleName', LMiddleName) then
WriteLn('Middle Name: ', LMiddleName)
else
WriteLn('Middle Name: (not specified)');
finally
LJSONObject.Free;
end;
ReadLn;
end.
Méthode 3 : FindValue, qui renvoie nil si rien n’est trouvé
Quand tu veux l’objet TJSONValue brut plutôt qu’une valeur convertie, utilise FindValue. La méthode renvoie nil si la clé n’existe pas, ne lève jamais d’exception, et te donne accès à toutes les propriétés et méthodes de la valeur JSON. Pratique quand tu dois vérifier le type réel d’une valeur, ou quand tu travailles sur des structures imbriquées compliquées :
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 renvoie nil si rien n'est trouvé (jamais d'exception)
LValue := LJSONObject.FindValue('name');
if LValue <> nil then
WriteLn('Name: ', LValue.Value);
LValue := LJSONObject.FindValue('nonexistent');
if LValue = nil then
WriteLn('Key "nonexistent" not found');
finally
LJSONObject.Free;
end;
ReadLn;
end.
Méthode 4 : la notation par chemin pour les valeurs imbriquées
Un des trucs vraiment pratiques de Delphi, c’est la notation par chemin : tu atteins une valeur profondément imbriquée avec un chemin séparé par des points, comme 'person.address.city', au lieu de traverser tous les objets intermédiaires. Ça marche avec TryGetValue, GetValue et FindValue, et ça t’évite des lignes et des lignes de navigation sur du JSON complexe :
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
// La notation par points atteint les valeurs imbriquées
if LJSONObject.TryGetValue<string>('person.name', LValue) then
WriteLn('Person Name: ', LValue);
if LJSONObject.TryGetValue<string>('person.address.city', LValue) then
WriteLn('City: ', LValue);
if LJSONObject.TryGetValue<string>('person.address.country', LValue) then
WriteLn('Country: ', LValue);
finally
LJSONObject.Free;
end;
ReadLn;
end.
Lire des tableaux, à l’ancienne et à la moderne
Quand ton JSON contient des tableaux, il faut parcourir leurs éléments pour traiter chaque valeur. Delphi gère les deux : la boucle classique par index avec Items[I], et le for-in moderne, qui marche sur n’importe quel TJSONArray. Le for-in est plus propre quand l’index ne t’intéresse pas, la boucle classique te donne la position. Pour un tableau de nombres, transtype chaque élément en TJSONNumber pour utiliser AsInt ou 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
// Lit un tableau de chaînes, boucle for classique
if LJSONObject.TryGetValue<TJSONArray>('skills', LSkills) then
begin
WriteLn('Skills (classic loop):');
for I := 0 to LSkills.Count - 1 do
WriteLn(' ', I + 1, '. ', LSkills.Items[I].Value);
end;
WriteLn;
// Lit un tableau de chaînes, boucle for-in moderne (Delphi XE+)
if LJSONObject.TryGetValue<TJSONArray>('skills', LSkills) then
begin
WriteLn('Skills (for-in loop):');
for LItem in LSkills do
WriteLn(' - ', LItem.Value);
end;
WriteLn;
// Lit un tableau numérique
if LJSONObject.TryGetValue<TJSONArray>('scores', LScores) then
begin
WriteLn('Scores:');
for LItem in LScores do
WriteLn(' Score: ', (LItem as TJSONNumber).AsInt);
end;
finally
LJSONObject.Free;
end;
ReadLn;
end.
Parcourir les paires d’un objet JSON
Parfois tu dois traiter toutes les propriétés d’un objet JSON sans connaître les noms des clés à l’avance : un visualiseur JSON générique, ou une structure dynamique. Le for-in marche sur TJSONObject comme sur les tableaux, et à chaque tour tu récupères un TJSONPair. Chaque paire te donne la clé (via JsonString.Value) et la valeur (via JsonValue), et tu peux regarder le type à l’exécution avec 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('All pairs in object:');
WriteLn;
// Parcourt toutes les paires avec for-in
for LPair in LJSONObject do
begin
WriteLn('Key: ', LPair.JsonString.Value);
WriteLn('Value: ', LPair.JsonValue.ToString);
WriteLn('Type: ', LPair.JsonValue.ClassName);
WriteLn;
end;
finally
LJSONObject.Free;
end;
ReadLn;
end.
Modifier des objets JSON
En Delphi, un objet JSON est entièrement modifiable : tu ajoutes, tu enlèves et tu mets à jour des propriétés après coup. Les règles de gestion mémoire comptent ici : quand tu appelles RemovePair, la propriété de la paire te revient, c’est donc à toi de la libérer. Appeler Free sur nil ne pose aucun problème en Delphi : l’écriture RemovePair('key').Free marche donc même si la clé n’existe pas.
Ajouter et enlever des paires
Le cycle complet de modification d’un objet JSON : créer les propriétés de départ, en supprimer une, en mettre une autre à jour. Regarde bien la mise à jour : tu enlèves d’abord l’ancienne paire (et tu la libères), puis tu en ajoutes une nouvelle avec la même clé.
program JSONModify;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LJSONObject: TJSONObject;
LRemovedPair: TJSONPair;
begin
LJSONObject := TJSONObject.Create;
try
// Ajoute les paires de départ
LJSONObject.AddPair('name', 'Daniele');
LJSONObject.AddPair('city', 'Rome');
LJSONObject.AddPair('temp', 'to be removed');
WriteLn('Initial:');
WriteLn(LJSONObject.ToString);
WriteLn;
// Enlève une paire, RemovePair renvoie la paire enlevée (elle est à toi !)
LRemovedPair := LJSONObject.RemovePair('temp');
LRemovedPair.Free; // Sûr même si nil, Free teste Self <> nil
WriteLn('After removing "temp":');
WriteLn(LJSONObject.ToString);
WriteLn;
// Pour mettre à jour une valeur : enlever puis ajouter
LRemovedPair := LJSONObject.RemovePair('city');
LRemovedPair.Free;
LJSONObject.AddPair('city', 'Milan');
WriteLn('After updating "city":');
WriteLn(LJSONObject.ToString);
finally
LJSONObject.Free;
end;
ReadLn;
end.
Sortie :
Initial:
{"name":"Daniele","city":"Rome","temp":"to be removed"}
After removing "temp":
{"name":"Daniele","city":"Rome"}
After updating "city":
{"name":"Daniele","city":"Milan"}
Cloner un objet JSON
Quand tu veux une version modifiée d’un objet JSON sans toucher à l’original, utilise Clone. Tu obtiens une copie profonde : un arbre d’objets totalement indépendant, où les modifications du clone ne touchent pas l’original, et réciproquement. Indispensable quand tu reçois du JSON que tu dois transformer avant de le renvoyer ailleurs tout en gardant l’original intact :
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 crée une copie indépendante
LClone := LOriginal.Clone as TJSONObject;
try
// Modifie le clone, l'original n'est pas touché
LPair := LClone.RemovePair('city');
LPair.Free;
LClone.AddPair('city', 'Milan');
WriteLn('Original: ', LOriginal.ToString);
WriteLn('Clone: ', LClone.ToString);
finally
LClone.Free;
end;
finally
LOriginal.Free;
end;
ReadLn;
end.
Sortie :
Original: {"name":"Daniele","city":"Rome"}
Clone: {"name":"Daniele","city":"Milan"}
Travailler avec des fichiers JSON
Écrire du JSON sur disque, c’est le quotidien : fichiers de configuration, cache, export de données. L’unité System.IOUtils de Delphi fournit la classe TFile avec des méthodes simples de lecture et d’écriture de fichiers texte, ce qui tombe bien : un document JSON n’est que du texte.
Enregistrer du JSON dans un fichier
Pour enregistrer un objet JSON dans un fichier, convertis-le en chaîne avec Format() (sortie lisible) ou ToString() (sortie compacte), puis écris cette chaîne sur disque. TPath.GetDocumentsPath t’assure un emplacement accessible en écriture quelle que soit la configuration de 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);
// Enregistre dans le fichier
{$IF CompilerVersion >= 33.0}
TFile.WriteAllText(LFileName, LJSONObject.Format());
{$ELSE}
TFile.WriteAllText(LFileName, LJSONObject.ToString);
{$ENDIF}
WriteLn('Saved to: ', LFileName);
finally
LJSONObject.Free;
end;
ReadLn;
end.
Charger du JSON depuis un fichier
La lecture est tout aussi directe : tu lis le contenu du fichier dans une chaîne et tu la passes à ParseJSONValue. Vérifie d’abord que le fichier existe, pour éviter une exception, puis que le parsing a réussi avant d’accéder aux données. La notation par chemin marche sur du JSON parsé exactement comme sur un objet construit à la main :
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 not found: ', LFileName);
ReadLn;
Exit;
end;
LContent := TFile.ReadAllText(LFileName);
LJSONValue := TJSONObject.ParseJSONValue(LContent);
if LJSONValue = nil then
begin
WriteLn('Invalid JSON in file!');
ReadLn;
Exit;
end;
try
LJSONObject := LJSONValue as TJSONObject;
if LJSONObject.TryGetValue<string>('appName', LAppName) then
WriteLn('App Name: ', LAppName);
if LJSONObject.TryGetValue<Integer>('database.port', LPort) then
WriteLn('Database Port: ', LPort);
finally
LJSONValue.Free;
end;
ReadLn;
end.
Transformer des objets en JSON avec REST.Json
Tout ce qui précède construit le JSON à la main, paire par paire. C’est ce qu’il faut faire quand la forme du document est le sujet. C’est ce qu’il ne faut pas faire quand tu as déjà une classe et que tu veux juste l’envoyer sur le réseau.
Pour ça, il y a REST.Json, livrée avec Delphi depuis XE5. Aucun code tiers à ajouter, et l’essentiel du travail tient en un appel.
De l’objet au 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));
Sortie :
{"id":42,"name":"Daniele Teti","active":true,"address":{"city":"Roma","zipCode":"00100"},"vat_number":"IT01234567890"}
Il s’est passé trois choses.
Le TAddress imbriqué a été sérialisé lui aussi, sans que tu le demandes. REST.Json parcourt le graphe d’objets.
InternalNote n’est pas dans la sortie. [JSONMarshalled(False)] est la façon de garder un champ hors du document, et c’est l’attribut que tu veux sur tout ce que le client n’a pas à voir.
Les clés ressortent en id, name et zipCode. REST.Json lit les champs privés, pas les propriétés, enlève le préfixe F et met la première lettre en minuscule. Donc FZipCode devient zipCode, camelCase, que ça te plaise ou non. Quand l’autre bout de la ligne exige une autre orthographe, [JSONName('vat_number')] l’impose, un champ à la fois.
Du JSON vers l’objet
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
Remarque que l’entrée utilisait Id et Name avec une majuscule et que ça a marché quand même : la lecture est insensible à la casse, l’écriture non. L’objet revient entièrement construit, adresse imbriquée comprise, et c’est à toi de le libérer.
Les champs dont le JSON ne parle pas gardent ce que le constructeur y a laissé :
LCustomer := TJson.JsonToObject<TCustomer>('{"Name":"Only a name"}');
Id=0 Name=Only a name Active=False
Pas d’exception. Si un Id absent veut dire que quelque chose ne va pas en amont, c’est à toi de le vérifier.
Celui qui va te coûter un après-midi
Déclare ces classes dans le .dpr et TJson.JsonToObject échoue :
EConversionError: Internal: Cannot instantiate type restjson.TCustomer
Le sérialiseur a besoin du RTTI de la classe, lié dans le module, et un type déclaré dans le fichier programme n’en produit pas. Déplace les déclarations dans une unité et le même code marche. La sérialisation vers le JSON ne se plaint jamais, donc tu ne rencontres ça qu’au retour, en général après t’être convaincu que le JSON est malformé.
Encore un, propre aux versions récentes : TJson.Format est déprécié dans Delphi 13 Florence. Le compilateur te dit quoi utiliser à la place :
W1000 Symbol 'Format' is deprecated: 'Use TJSONAncestor.Format instead'
L’affichage indenté d’un objet, c’est donc maintenant :
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": ""
}
Là où REST.Json s’arrête
Sérialise une liste et tu tombes sur la limite :
LArray := TJSONArray.Create;
try
for LCustomer in LList do
LArray.AddElement(TJson.ObjectToJsonObject(LCustomer));
Writeln(LArray.ToJSON);
finally
LArray.Free;
end;
Ça marche, et c’est déjà une boucle que tu as écrite à la main. Dans l’autre sens, d’un tableau JSON vers une TObjectList<TCustomer>, REST.Json n’a rien à proposer du tout : tu parses le tableau toi-même et tu appelles JsonToObject sur chaque élément.
REST.Json est très bonne pour un objet à la fois, avec les noms qu’elle choisit à ta place. Au-delà, il te faut un sérialiseur fait pour ça.
Quand il t’en faut plus : les sérialiseurs de DelphiMVCFramework
DelphiMVCFramework livre un sérialiseur utilisable seul, sans serveur ni contrôleur en vue. C’est une unité et une interface.
Une liste, en un appel, dans les deux sens :
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"}]
Et le retour :
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
Deux appels là où REST.Json te donnait deux boucles. Remarque aussi que le TDateTime est sorti et revenu comme une vraie date, en ISO 8601 avec le décalage, ce qui est le sujet de la section suivante.
La casse des clés est une décision, ici, pas une règle qu’on t’impose. Mets l’attribut sur la classe et toute la classe suit :
[MVCNameCase(ncSnakeCase)]
TSnakeOrder = class
// ...
end;
[MVCNameCase(ncPascalCase)]
TPascalOrder = class
// ...
end;
ncSnakeCase : {"order_id":42,"customer_name":"Daniele Teti"}
ncPascalCase: {"OrderId":42,"CustomerName":"Daniele Teti"}
C’est celui-là qui tranche, pour la plupart des gens. Si l’API à laquelle tu dois parler veut order_id, REST.Json te donne un [JSONName] par champ, à vie ; le sérialiseur DMVCFramework te donne un attribut par classe.
Il y a un piège là-dedans, et il est silencieux. La casse des noms s’applique aussi en lecture. Le sérialiseur est en ncLowerCase par défaut, donc il émet placedat et attend placedat. Donne-lui le même payload avec placedAt et :
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, c’est le zéro de TDateTime. Pas d’erreur, pas d’avertissement, juste un champ qui n’est pas arrivé, sans bruit. Quand tu consommes une API que tu ne contrôles pas, règle la casse des noms sur la sienne et teste un payload de bout en bout avant de croire quoi que ce soit.
La même règle RTTI s’applique, au passage. Déclare TOrder dans le .dpr et tu obtiens :
Exception: Cannot find RTTI for dmvcser.TOrder. Hint: Is the specified classtype linked in the module?
Sérialiseur différent, message différent, même cause : les types vont dans des unités.
Les dates en JSON, et l’heure que tu vas perdre
JSON n’a pas de type date. Quoi que tu fasses, un TDateTime sort de ton processus sous forme de chaîne, et tout le monde s’est mis d’accord sur laquelle : ISO 8601. Delphi te donne la conversion dans System.DateUtils, et il te donne un défaut qui est faux pour l’essentiel du code que tu écris.
Le défaut, c’est 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
Le deuxième paramètre s’appelle AInputIsUTC et il vaut True par défaut. Donc DateToISO8601(SomeDate) annonce au monde entier que la valeur que tu as passée est déjà en UTC. Si elle vient de Now, d’un TDateTimePicker ou d’une colonne de base de données écrite par une application locale, elle n’est pas en UTC, et tu viens de tamponner un Z sur une heure locale.
Aucune exception, et le document est valide. L’heure est juste fausse.
Écrire et lire avec le même 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
Mélange les flags et la valeur se déplace de ton décalage par rapport à UTC, en silence :
LText := DateToISO8601(LOriginal, False); // locale
LBack := ISO8601ToDate(LText); // défaut, la traite comme de l'UTC
written with False, read with the default:
15/03/2023 12:00:00 -> 15/03/2023 11:00:00
drift in minutes: 60
Une heure, sur une machine en Italie au mois de mars. En août, deux. Sur une machine en UTC, zéro, et c’est exactement pour ça que le décalage passe les tests et ressort chez un client.
Dans un document
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
Une entrée que tu n’as pas écrite
ISO8601ToDate lève une exception sur tout ce qu’elle n’arrive pas à lire. Pour un payload arrivé par le réseau, utilise la version 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
Même forme que TryGetValue plus haut dans cet article, et la même raison de la préférer.
Choisis UTC ou local une fois pour toutes, pour toute l’application, et passe le flag explicitement à chaque appel. Le défaut ne sera pas celui que tu voulais.
Exemple concret : un client d’API REST
Assemble tout ça dans un cas réel : appelle une API REST et traite la réponse JSON. L’exemple se connecte à JSONPlaceholder (une API de test gratuite), récupère une liste d’utilisateurs et transforme chacun en record Delphi. Remarque l’usage systématique de TryGetValue pour absorber les champs éventuellement absents, obligatoire face à une API externe qui peut changer sans te prévenir.
THTTPClient demande Delphi XE8 ou plus récent.
program JSONRestApiClient;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON,
System.Net.HttpClient; // Demande Delphi XE8+
type
TUser = record
ID: Integer;
Name: string;
Email: string;
Username: string;
end;
function ParseUser(AJSONObject: TJSONObject): TUser;
begin
// Passe par TryGetValue par sécurité
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('Fetching users from JSONPlaceholder API...');
WriteLn;
LClient := THTTPClient.Create;
try
LResponse := LClient.Get('https://jsonplaceholder.typicode.com/users');
if LResponse.StatusCode <> 200 then
begin
WriteLn('HTTP Error: ', LResponse.StatusCode);
ReadLn;
Exit;
end;
// Parse la réponse, un tableau JSON
LJSONValue := TJSONObject.ParseJSONValue(LResponse.ContentAsString);
if LJSONValue = nil then
begin
WriteLn('Invalid JSON response');
ReadLn;
Exit;
end;
try
if not (LJSONValue is TJSONArray) then
begin
WriteLn('Expected JSON array');
Exit;
end;
LJSONArray := LJSONValue as TJSONArray;
WriteLn('Found ', LJSONArray.Count, ' users:');
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('Name: ', LUser.Name);
WriteLn('Email: ', LUser.Email);
WriteLn('Username: ', LUser.Username);
WriteLn(StringOfChar('-', 50));
end;
finally
LJSONValue.Free;
end;
finally
LClient.Free;
end;
ReadLn;
end.
Exemple concret : un gestionnaire de fichier de configuration
Dernier exemple : une classe complète et réutilisable pour gérer la configuration d’une application. TConfigManager encapsule toute la mécanique de chargement, d’enregistrement et de lecture des paramètres derrière une API propre et typée, avec chargement paresseux (le fichier n’est lu qu’au premier besoin), valeurs par défaut pour les clés absentes et création automatique du fichier. Prends-le comme point de départ pour ton propre système de configuration :
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;
// Démonstration d'usage
var
Config: TConfigManager;
LConfigFile: string;
begin
LConfigFile := TPath.Combine(TPath.GetDocumentsPath, 'appsettings.json');
WriteLn('Config file: ', LConfigFile);
WriteLn;
Config := TConfigManager.Create(LConfigFile);
try
// Écrit quelques valeurs (les clés sont à plat, pas d'objets imbriqués)
Config.SetValue('databaseHost', 'localhost');
Config.SetValue('databasePort', 5432);
Config.SetValue('databaseName', 'myapp');
Config.SetValue('loggingEnabled', True);
Config.SetValue('loggingMaxFiles', 10);
Config.Save;
WriteLn('Configuration saved!');
WriteLn;
// Relit les valeurs
WriteLn('Database Host: ', Config.GetString('databaseHost'));
WriteLn('Database Port: ', Config.GetInteger('databasePort'));
WriteLn('Logging Enabled: ', Config.GetBoolean('loggingEnabled'));
// Lecture avec valeur par défaut
WriteLn('Timeout (default 30): ', Config.GetInteger('timeout', 30));
finally
Config.Free;
end;
ReadLn;
end.
Sortie :
Config file: C:\Users\yourname\Documents\appsettings.json
Configuration saved!
Database Host: localhost
Database Port: 5432
Logging Enabled: TRUE
Timeout (default 30): 30
Bibliothèques JSON tierces
Le parser JSON intégré de Delphi fait très bien le travail dans la majorité des cas, mais certaines situations gagnent à passer par une bibliothèque tierce :
| Bibliothèque | Idéale pour | URL |
|---|---|---|
| JsonDataObjects | Les performances, utilisée par DelphiMVCFramework | GitHub |
| Grijjy Foundation | Complète, avec le support BSON | GitHub |
| mORMot2 | Framework complet (ORM, SOA, REST) qui apporte sa propre couche JSON | GitHub |
Quand sortir une bibliothèque tierce
- Gros fichiers JSON (plus de 10 Mo) : regarde du côté des parsers en streaming ou de JsonDataObjects
- Parsing à haute fréquence : JsonDataObjects, mesurée face à
System.JSONplus bas dans cet article - Besoin de BSON : Grijjy Foundation
- Sérialisation d’objets :
REST.Jsonpour les cas simples, les sérialiseurs de DelphiMVCFramework pour les listes, les datasets et le contrôle de la casse des noms
Pour la plupart des applications, System.JSON suffit, et tu n’ajoutes aucune dépendance externe.
System.JSON, quelle vitesse au juste
« Prends une bibliothèque tierce si tu as besoin de performance », c’est facile à écrire et difficile à appliquer. Voilà des chiffres.
Le test construit un tableau de 50 000 records, chacun avec un entier, deux chaînes, un booléen, un nombre et un horodatage ISO 8601 : 14 Mo de texte UTF-16, la forme d’un vrai export. Puis il mesure deux opérations. Parser et lire un entier dans chaque record, ce que fait un client. Et parser puis sérialiser à nouveau dans la foulée, ce que fait un proxy.
Chaque exécution est précédée d’un préchauffage qui part à la poubelle, et c’est le meilleur des sept qui est retenu, donc les chiffres sont le plancher, pas une moyenne de ce que la machine faisait à côté.
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, optimisations activées, contrôles de plage et de débordement désactivés, sur un Core i9-13980HX sous Windows 11.
Donc : JsonDataObjects parse à peu près quatre fois plus vite, et fait l’aller-retour à peu près deux fois et demie plus vite. L’écart est réel et il tient d’une exécution à l’autre.
System.JSON a quand même avalé 14 Mo en un sixième de seconde environ. Si ton JSON fait quelques centaines de kilo-octets, ce qui couvre la plupart des réponses REST et quasiment tous les fichiers de configuration, tu choisis entre deux millisecondes et une demi-milliseconde. Ce n’est pas une décision, c’est une erreur d’arrondi, et System.JSON est déjà installée.
Sors JsonDataObjects quand le payload se mesure en méga-octets, ou quand tu parses dans une boucle qui tourne des milliers de fois, ou quand tu es sur un appareil où le CPU n’est pas gratuit. Sinon la dépendance te coûte plus qu’elle ne te rapporte.
Une note pratique si tu l’ajoutes : JsonDataObjects déclare ses propres TJSONObject et TJSONArray. Dans une unité qui utilise les deux, c’est le dernier de la clause uses qui gagne, et tu récoltes des erreurs qui n’ont aucun sens tant que tu n’as pas vu ça :
E2003 Undeclared identifier: 'ParseJSONValue'
E2010 Incompatible types: 'System.JSON.TJSONValue' and 'JsonDataObjects.TJsonArray'
Qualifie les noms de types, System.JSON.TJSONObject et JsonDataObjects.TJsonArray, et l’ambiguïté disparaît.
mORMot2 n’est pas dans cette comparaison. C’est un framework complet plutôt qu’une bibliothèque JSON, et le mesurer honnêtement suppose d’embarquer et de configurer l’ensemble, ce qui ferait un autre article.
Construire des API REST avec JSON
Si tu écris des API REST en Delphi, DelphiMVCFramework apporte un très bon support JSON avec sérialisation automatique :
[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); // Sérialisation JSON automatique
end;
Regarde les exemples de DelphiMVCFramework pour du code complet, et le guide officiel si tu préfères qu’on t’explique les sérialiseurs plutôt que de les deviner.
Questions fréquentes
Comment parser une chaîne JSON en Delphi ?
Avec TJSONObject.ParseJSONValue(), de l’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')); // Affiche : John
finally
LJSONObject.Free;
end;
end;
end;
Comment gérer les valeurs null en JSON ?
Passe par TryGetValue, qui gère les valeurs absentes ou null sans lever d’exception :
var
LValue: string;
begin
if LJSONObject.TryGetValue<string>('optionalField', LValue) then
WriteLn('Value: ', LValue)
else
WriteLn('Field is missing or null');
end;
Comment parcourir un tableau JSON ?
Avec la syntaxe moderne du 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;
Quelle différence entre Format() et ToString() ?
Format(): renvoie du JSON indenté, lisible par un humain (Delphi 10.3+ uniquement)ToString(): renvoie du JSON compact, sans espaces (mieux pour le transfert réseau, marche dans toutes les versions)
Comment modifier un objet JSON existant ?
Avec RemovePair puis AddPair. RemovePair renvoie la paire enlevée (ou nil si elle n’existe pas) : elle t’appartient, c’est à toi de la libérer.
begin
// Remove renvoie la paire, tu dois la libérer !
// Free est sûr sur nil (il teste Self <> nil en interne)
LJSONObject.RemovePair('name').Free;
// Ajoute la nouvelle valeur
LJSONObject.AddPair('name', 'New Value');
end;
Quelle version de Delphi a introduit le support JSON ?
- Delphi 2009 : premier support JSON, dans l’unité
DBXJSON - Delphi XE6 : l’unité est renommée
System.JSON, avec une API améliorée - Delphi 10.1 Berlin : l’API fluide
TJSONObjectBuilder - Delphi 10.3 Rio : la méthode
Format()etEJSONParseExceptionavec le détail de l’erreur
Quelle différence entre GetValue, FindValue et TryGetValue ?
| Méthode | Renvoie | Si la clé n’existe pas |
|---|---|---|
GetValue<T>('key') |
Une valeur de type T | Lève une exception |
FindValue('key') |
TJSONValue ou nil | Renvoie nil |
TryGetValue<T>('key', outVar) |
Boolean | Renvoie False |
Recommandation : en production, utilise TryGetValue, c’est la plus sûre.
Comment faire une copie profonde d’un objet JSON ?
Avec la méthode Clone :
var
LOriginal, LCopy: TJSONObject;
begin
LOriginal := TJSONObject.ParseJSONValue('{"name":"test"}') as TJSONObject;
try
LCopy := LOriginal.Clone as TJSONObject;
try
// LCopy est indépendant, le modifier ne touche pas LOriginal
finally
LCopy.Free;
end;
finally
LOriginal.Free;
end;
end;
Comment savoir si une valeur JSON est null ?
var
LValue: TJSONValue;
begin
LValue := LJSONObject.FindValue('myField');
if LValue = nil then
WriteLn('Field does not exist')
else if LValue is TJSONNull then
WriteLn('Field exists but is null')
else
WriteLn('Field has a value: ', LValue.Value);
end;
La notation par chemin marche-t-elle sur les éléments d’un tableau ?
Oui, avec les crochets et l’index :
var
LFirstSkill: string;
begin
// Accède au premier élément du tableau skills
if LJSONObject.TryGetValue<string>('skills[0]', LFirstSkill) then
WriteLn('First skill: ', LFirstSkill);
end;
Comment convertir un objet Delphi en JSON ?
Avec TJson.ObjectToJsonString de REST.Json, livrée avec Delphi. Elle parcourt le graphe d’objets, lit les champs privés et te donne des clés en camelCase ; [JSONName] renomme un champ et [JSONMarshalled(False)] en exclut un. Pour les listes, le contrôle de la casse des noms et les datasets, passe par les sérialiseurs de DelphiMVCFramework. Voir Transformer des objets en JSON avec REST.Json plus haut.
System.JSON est-elle assez rapide ?
Pour presque tout, oui. Sur 14 Mo de JSON, 50 000 records, System.JSON parse et lit en 158 ms environ ; JsonDataObjects fait le même travail en 42 ms, à peu près quatre fois plus vite. Sur un payload de quelques centaines de kilo-octets, ce qui couvre la plupart des réponses REST et tous les fichiers de configuration, la différence est une fraction de milliseconde. Change de bibliothèque quand les documents se mesurent en méga-octets ou que tu parses dans une boucle serrée, pas par défaut. Les chiffres et la méthode sont dans System.JSON, quelle vitesse au juste.
Comment sérialiser une TObjectList en JSON ?
REST.Json ne gère pas les listes : tu boucles, tu appelles TJson.ObjectToJsonObject sur chaque élément et tu les ajoutes un par un à un TJSONArray. Dans l’autre sens, elle n’a rien du tout, donc tu parses le tableau et tu appelles JsonToObject sur chaque élément. Le sérialiseur DelphiMVCFramework fait les deux en un appel, SerializeCollection et DeserializeCollection.
Pourquoi TJson.JsonToObject lève-t-elle « Cannot instantiate type » ?
Parce que la classe est déclarée dans le fichier programme .dpr, qui ne produit pas de RTTI lié. Déplace la déclaration du type dans une unité et le même code marche. La sérialisation vers le JSON ne se plaint jamais, donc l’erreur n’apparaît qu’au retour. Le sérialiseur DelphiMVCFramework échoue sur la même cause avec un autre message, Cannot find RTTI for ....
System.JSON est-elle thread-safe ?
Non. TJSONObject et les classes associées ne sont pas thread-safe. Si plusieurs threads doivent toucher au même objet JSON, la synchronisation est à ta charge (sections critiques, verrous, etc.). En lecture seule après le parsing initial, tu peux partager l’objet entre threads tant que personne ne le modifie.
Comment sérialiser un TDateTime en JSON ?
TJSONObject n’a pas de surcharge AddPair pour TDateTime. Convertis d’abord en chaîne ISO 8601, et passe AInputIsUTC explicitement, parce qu’il vaut True par défaut et va étiqueter une heure locale comme de l’UTC. Voir Les dates en JSON, et l’heure que tu vas perdre :
LJSONObject.AddPair('createdAt', FormatDateTime('yyyy-mm-dd"T"hh:nn:ss', Now));
Quelle taille de JSON Delphi peut-il parser ?
Il n’y a pas de limite fixe, mais System.JSON charge tout le document en mémoire. Pour les très gros fichiers (plus de 100 Mo), regarde les parsers en streaming comme TJsonTextReader de System.JSON.Readers, ou les bibliothèques tierces optimisées pour les gros documents.
Quelle différence entre System.JSON et DBXJSON ?
C’est la même bibliothèque, simplement renommée. DBXJSON était le nom de l’unité de Delphi 2009 à XE5. À partir de Delphi XE6, elle est devenue System.JSON pour suivre les nouvelles conventions de nommage. L’API est pour ainsi dire identique, migrer du vieux code ne pose pas de difficulté.
Comment afficher du JSON indenté en Delphi ?
Avec la méthode Format() (Delphi 10.3+), qui renvoie du JSON indenté et lisible :
WriteLn(LJSONObject.Format()); // Indenté, lisible
WriteLn(LJSONObject.ToString); // Compact, sur une seule ligne
Sur les versions plus anciennes de Delphi, il faut passer par une bibliothèque tierce ou écrire ton propre formatage.
Comment gérer les caractères spéciaux et l’Unicode en JSON ?
System.JSON gère l’Unicode tout seul et échappe les caractères spéciaux à la génération. Au parsing, il convertit sans problème les séquences échappées comme \n, \t et \uXXXX. Tu n’as rien à faire à la main :
LJSONObject.AddPair('message', 'Line 1'#13#10'Line 2'); // Sauts de ligne échappés automatiquement
LJSONObject.AddPair('emoji', '🚀'); // L'Unicode passe directement
Comment fusionner deux objets JSON ?
Il n’y a pas de fonction de fusion intégrée. Parcours un objet et ajoute ses paires à l’autre :
for LPair in LSource do
LTarget.AddPair(LPair.JsonString.Value, LPair.JsonValue.Clone as TJSONValue);
Note : tu dois cloner les valeurs, parce qu’elles ne peuvent appartenir qu’à un seul objet parent.
Comment valider du JSON avant de le parser ?
ParseJSONValue renvoie nil sur du JSON invalide, ce qui te donne une validation de base. Pour valider un schéma (structure, champs obligatoires, types), il te faut une bibliothèque tierce : Delphi n’embarque pas le support de JSON Schema.
Comment accéder à des tableaux profondément imbriqués ?
Combine la notation par chemin et l’indexation :
// Accès à : {"data": {"users": [{"name": "Alice"}, {"name": "Bob"}]}}
if LJSONObject.TryGetValue<string>('data.users[1].name', LValue) then
WriteLn(LValue); // Affiche : Bob
Peut-on utiliser JSON avec les datasets FireDAC ?
Oui, mais il n’y a pas d’intégration directe. Tu peux parcourir le dataset à la main et construire le JSON, ou passer par une bibliothèque de sérialisation. DelphiMVCFramework et mORMot2 fournissent tous les deux la sérialisation dataset vers JSON en standard.
Comment gérer du JSON avec des clés en double ?
Techniquement, JSON autorise les clés en double, même si c’est déconseillé. TJSONObject stocke toutes les paires, mais GetValue/TryGetValue ne renvoient que la première trouvée. Pour récupérer toutes les valeurs d’une même clé, parcours l’objet avec une boucle for-in.
Résumé
Delphi embarque un support JSON solide avec l’unité System.JSON. À retenir :
TJSONObjectetTJSONArraypour créer et parser du JSON- Teste toujours nil quand tu parses une chaîne JSON
TryGetValuepour lire sans risque, surtout des champs optionnels- La notation par chemin (
'parent.child') pour les valeurs imbriquées - La mémoire : l’objet parent possède ses enfants,
RemovePairte rend la propriété - Les bibliothèques tierces seulement pour un besoin de performance précis
Format()pour une sortie lisible (Delphi 10.3+),ToString()pour une sortie compacte- Les boucles for-in pour parcourir proprement tableaux et paires
Pour écrire des API REST modernes en Delphi, va voir DelphiMVCFramework : il embarque une sérialisation JSON avancée et tourne en production dans des entreprises du monde entier.
Articles liés :
- Building Delphi Web Applications with DMVCFramework and TemplatePro : le guide complet du développement web
- How to Serialize a TList of Objects with Delphi : les techniques de sérialisation avancées
- Custom Marshalling/Unmarshalling in Delphi : tout sur le traitement JSON personnalisé
Comments
comments powered by Disqus