JSON in Delphi: Kompletter Leitfaden mit Beispielen (2026)
🇬🇧 English • 🇮🇹 Italiano • 🇪🇸 Español • 🇫🇷 Français
JSON (JavaScript Object Notation) ist der De-facto-Standard für den Datenaustausch. Ob du REST-APIs baust, Konfigurationsdateien liest oder mit Webdiensten sprichst: Du kommst an JSON in Delphi nicht vorbei.
Dieser Leitfaden zeigt dir die JSON-Unterstützung in Delphi, mit vollständigen, kompilierbaren Beispielen, die du direkt in deine Projekte übernehmen kannst.
TJSONObject, TJSONArray). Für Streaming- bzw. SAX-artiges Parsen schau dir die Units System.JSON.Readers und System.JSON.Writers an.
Kompatibilität mit Delphi-Versionen
Die JSON-Unterstützung hat sich über die Delphi-Versionen hinweg deutlich weiterentwickelt:
| Version | Unit | Wichtigste Neuerungen |
|---|---|---|
| Delphi 2009 | DBXJSON |
Erste JSON-Unterstützung mit einfachen Basisklassen |
| Delphi XE6 | System.JSON |
Unit umbenannt, verbesserte API |
| Delphi 10.1 Berlin | System.JSON |
Fluent-API TJSONObjectBuilder, Verbesserungen an TryGetValue<T> |
| Delphi 10.3 Rio | System.JSON |
Methode Format(), EJSONParseException mit Details, schnellere Verarbeitung |
| Delphi 11-12 | System.JSON |
Weitere Optimierungen und Feinschliff |
| Delphi 13 Florence | System.JSON |
Aktuelle Verbesserungen und weiterhin gepflegte Unterstützung |
Was ist JSON?
JSON ist ein schlankes, textbasiertes Format für den Datenaustausch. Menschen können es lesen und schreiben, Maschinen können es parsen und erzeugen. Ein JSON-Dokument kann enthalten:
- Objekte: Schlüssel-Wert-Paare in geschweiften Klammern
{} - Arrays: geordnete Listen von Werten in eckigen Klammern
[] - Werte: Strings, Zahlen, Booleans (
true/false),null, Objekte oder Arrays
Beispiel für eine JSON-Struktur:
{
"name": "Daniele Teti",
"age": 45,
"active": true,
"skills": ["Delphi", "Python", "SQL"],
"address": {
"city": "Rome",
"country": "Italy"
}
}
Die JSON-Klassen von Delphi im Überblick
Delphi bringt JSON von Haus aus mit, über die Unit System.JSON. Die wichtigsten Klassen:
| Klasse | Beschreibung |
|---|---|
TJSONValue |
Basisklasse für alle JSON-Werttypen |
TJSONObject |
steht für ein JSON-Objekt (Schlüssel-Wert-Paare) |
TJSONArray |
steht für ein JSON-Array (geordnete Liste) |
TJSONString |
steht für einen JSON-String-Wert |
TJSONNumber |
steht für einen numerischen JSON-Wert |
TJSONBool |
steht für einen booleschen JSON-Wert |
TJSONNull |
steht für den JSON-Wert null |
TJSONPair |
steht für ein Schlüssel-Wert-Paar in einem Objekt |
JSON-Objekte erzeugen
Zuerst die Grundlagen: ein JSON-Objekt anlegen und Eigenschaften hinzufügen.
Ein einfaches JSON-Objekt anlegen
Der Grundfall: Du erzeugst ein TJSONObject und hängst Schlüssel-Wert-Paare dran. Delphi hat praktische Überladungen von AddPair, die Strings, Integer, Booleans und Doubles direkt entgegennehmen. Du musst primitive Werte also nicht erst in JSON-Klassen einpacken. Das folgende Beispiel baut ein einfaches JSON-Objekt mit Personendaten in verschiedenen Datentypen:
program JSONCreateBasic;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LJSONObject: TJSONObject;
begin
LJSONObject := TJSONObject.Create;
try
// String-Eigenschaft hinzufügen
LJSONObject.AddPair('firstName', 'Daniele');
LJSONObject.AddPair('lastName', 'Teti');
// Numerische Eigenschaft (Überladungen für Integer, Int64, Double vorhanden)
LJSONObject.AddPair('age', 45);
// Boolesche Eigenschaft hinzufügen
LJSONObject.AddPair('active', True);
// null-Eigenschaft (keine Überladung, TJSONNull ist Pflicht)
LJSONObject.AddPair('middleName', TJSONNull.Create);
// JSON ausgeben
// Hinweis: Format() gibt es ab 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.
Ausgabe:
{
"firstName": "Daniele",
"lastName": "Teti",
"age": 45,
"active": true,
"middleName": null
}
JSON-Arrays erzeugen
JSON-Arrays sind geordnete Sammlungen und können jede Kombination von Werten aufnehmen: Strings, Zahlen, Booleans, sogar weitere Arrays und Objekte. Wenn du ein TJSONArray per AddPair an ein TJSONObject hängst, übernimmt das Elternobjekt den Besitz, du gibst also nur das Wurzelobjekt frei. Dieses Beispiel zeigt ein homogenes String-Array und eines mit gemischten Typen:
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');
// String-Array anlegen
LSkills := TJSONArray.Create;
LJSONObject.AddPair('skills', LSkills);
LSkills.Add('Delphi');
LSkills.Add('Python');
LSkills.Add('SQL');
// Array mit gemischten Typen anlegen
LContacts := TJSONArray.Create;
LJSONObject.AddPair('contacts', LContacts);
LContacts.Add('daniele@example.com'); // String
LContacts.Add(123456); // Zahl
LContacts.Add(True); // Boolean
{$IF CompilerVersion >= 33.0}
WriteLn(LJSONObject.Format());
{$ELSE}
WriteLn(LJSONObject.ToString);
{$ENDIF}
finally
LJSONObject.Free; // gibt auch LContacts und LSkills frei
end;
ReadLn;
end.
Ausgabe:
{
"name": "Daniele Teti",
"skills": [
"Delphi",
"Python",
"SQL"
],
"contacts": [
"daniele@example.com",
123456,
true
]
}
Ein Array aus Objekten erzeugen
Eines der häufigsten Muster in echtem JSON ist ein Array mit mehreren Objekten: eine Liste von Benutzern, Produkten oder was auch immer du an Datensätzen hast. Jedes Objekt im Array hat seine eigenen Eigenschaften. Beim Aufbau erzeugst du jedes Objekt einzeln und hängst es mit Add an das Array. Das Array besitzt danach alle Objekte, die du hineingelegt hast:
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);
// Erster Benutzer
LUser := TJSONObject.Create;
LUsers.Add(LUser);
LUser.AddPair('id', 1);
LUser.AddPair('name', 'Alice');
LUser.AddPair('email', 'alice@example.com');
// Zweiter Benutzer
LUser := TJSONObject.Create;
LUsers.Add(LUser);
LUser.AddPair('id', 2);
LUser.AddPair('name', 'Bob');
LUser.AddPair('email', 'bob@example.com');
// Dritter Benutzer
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.
Ausgabe:
{
"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"
}
]
}
Verschachtelte JSON-Objekte
Komplexe Daten brauchen meistens eine Hierarchie: eine Person hat eine Adresse, die Adresse hat Stadt und Land, und so weiter. In Delphi baust du verschachtelte Strukturen, indem du TJSONObject-Instanzen als Werte in andere Objekte legst. Wie bei Arrays übernimmt das Elternobjekt den Besitz seiner Kinder, das macht die Speicherverwaltung einfach. Dieses Beispiel legt eine Person mit verschachteltem Adress- und Firmenobjekt an:
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');
// Verschachteltes Adressobjekt anlegen
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');
// Noch ein verschachteltes Objekt
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.
Ausgabe:
{
"name": "Daniele Teti",
"address": {
"street": "Via Roma 123",
"city": "Rome",
"country": "Italy",
"zipCode": "00100"
},
"company": {
"name": "bit Time Professionals",
"website": "https://www.bittime.it"
}
}
TJSONObjectBuilder verwenden (ab Delphi 10.1 Berlin)
Wenn du lieber deklarativ und verkettet baust: Delphi 10.1 Berlin hat TJSONObjectBuilder eingeführt. Mit dieser Fluent-API baust du komplexe JSON-Strukturen in einem einzigen Ausdruck, per Methodenkette aus BeginObject, BeginArray, Add und EndObject/EndArray. Der Builder schreibt in einen TJsonTextWriter, der wiederum in einen TStringBuilder ausgibt. Das Setup ist länger, aber bei komplexen Strukturen wird der Code sauberer und besser lesbar:
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
// JSON mit der Fluent-API bauen
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.
Ausgabe:
{
"firstName": "Daniele",
"lastName": "Teti",
"age": 45,
"active": true,
"address": {
"city": "Rome",
"country": "Italy"
},
"skills": [
"Delphi",
"Python",
"SQL"
]
}
JSON-Strings parsen
Wenn du JSON von einem Webdienst, aus einer Datei oder von sonstwo bekommst, musst du es in Delphi-Objekte umwandeln, mit denen du arbeiten kannst. Dafür gibt es die Klassenmethode TJSONObject.ParseJSONValue. Sie liefert ein TJSONValue zurück (die Basisklasse), du musst also prüfen, ob es der erwartete Typ ist, normalerweise TJSONObject oder TJSONArray. Ist das JSON kaputt, kommt nil zurück, also prüfe das immer, bevor du weitermachst:
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 liefert TJSONValue, auf den passenden Typ casten
LJSONValue := TJSONObject.ParseJSONValue(JSON_STRING);
if LJSONValue = nil then
begin
WriteLn('ERROR: Invalid JSON!');
ReadLn;
Exit;
end;
try
// Prüfen, ob es ein Objekt ist (die Wurzel kann auch ein Array sein)
if not (LJSONValue is TJSONObject) then
begin
WriteLn('ERROR: Expected JSON object at root level');
Exit;
end;
LJSONObject := TJSONObject(LJSONValue); // Harter Cast, nach dem "is" sicher
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 nil zurückliefert: Das heißt, das JSON ist ungültig.
Parse-Fehler behandeln (ab Delphi 10.3)
Wenn das Parsen schiefgeht, hilft beim Debuggen vor allem eins: zu wissen, warum. Ab Delphi 10.3 Rio kannst du ParseJSONValue als zweiten Parameter ein True mitgeben, dann wirft die Methode eine EJSONParseException, statt nil zurückzugeben. Die Exception enthält die Fehlermeldung, den Pfad, an dem das Parsen gescheitert ist, und den Zeichen-Offset. Bei komplexem oder fremdem JSON ist das Gold wert:
program JSONParseErrors;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
INVALID_JSON = '{"name": "Test", "value": }'; // Ungültig!
var
LJSONValue: TJSONValue;
begin
{$IF CompilerVersion >= 33.0} // Delphi 10.3 Rio
try
// Mit der Option RaiseExc kommt eine Exception mit Details
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}
// Vor 10.3: nur auf nil prüfen
LJSONValue := TJSONObject.ParseJSONValue(INVALID_JSON);
if LJSONValue = nil then
WriteLn('Invalid JSON - no details available')
else
LJSONValue.Free;
{$ENDIF}
ReadLn;
end.
JSON-Werte lesen
Es gibt mehrere Wege, Werte aus JSON-Objekten zu lesen, jeder mit einem anderen Kompromiss zwischen Bequemlichkeit und Sicherheit. Wenn du weißt, wann du welchen nimmst, schreibst du robusteren Code, der mit fehlenden oder unerwarteten Daten klarkommt.
Weg 1: GetValue mit generischem Typ (ab Delphi XE7)
Am einfachsten liest du einen Wert mit der generischen Methode GetValue<T>. Du gibst den erwarteten Typ als Typparameter an, Delphi kümmert sich um die Umwandlung. Allerdings fliegt eine Exception, wenn der Schlüssel nicht existiert, nimm sie also nur, wenn du sicher bist, dass der Schlüssel da ist:
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> wirft eine Exception, wenn der Schlüssel fehlt
WriteLn('Name: ', LJSONObject.GetValue<string>('name'));
WriteLn('Age: ', LJSONObject.GetValue<Integer>('age'));
WriteLn('Active: ', LJSONObject.GetValue<Boolean>('active'));
finally
LJSONObject.Free;
end;
ReadLn;
end.
Weg 2: TryGetValue, sicher lesen (empfohlen)
Im Produktivcode ist TryGetValue<T> der Weg der Wahl. Fehlt der Schlüssel oder lässt sich der Wert nicht in den gewünschten Typ umwandeln, kommt False zurück, und du behandelst den Fall ohne Exception-Handling. Besonders praktisch bei JSON aus fremder Quelle, wo du nicht garantieren kannst, dass alle Felder da sind:
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 liefert False, wenn der Schlüssel fehlt (keine 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');
// Diesen Schlüssel gibt es nicht, trotzdem keine Exception
if LJSONObject.TryGetValue<string>('middleName', LMiddleName) then
WriteLn('Middle Name: ', LMiddleName)
else
WriteLn('Middle Name: (not specified)');
finally
LJSONObject.Free;
end;
ReadLn;
end.
Weg 3: FindValue liefert nil, wenn nichts da ist
Wenn du nicht den umgewandelten Wert brauchst, sondern das rohe TJSONValue-Objekt, nimm FindValue. Die Methode liefert nil, wenn der Schlüssel fehlt, wirft nie eine Exception und gibt dir vollen Zugriff auf Eigenschaften und Methoden des JSON-Werts. Praktisch, wenn du den tatsächlichen Typ eines Werts prüfen willst oder mit komplexen verschachtelten Strukturen arbeitest:
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 liefert nil, wenn nichts gefunden wird (wirft nie eine 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.
Weg 4: Pfadnotation für verschachtelte Werte
Eines der bequemsten Features in Delphi ist die Pfadnotation: Du greifst auf tief verschachtelte Werte über punktgetrennte Pfade wie 'person.address.city' zu, statt dich durch mehrere Zwischenobjekte zu hangeln. Das geht mit TryGetValue, GetValue und FindValue und erspart dir eine Menge Navigationscode, wenn du einzelne Werte aus komplexem JSON ziehst:
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
// Mit Punktnotation auf verschachtelte Werte zugreifen
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.
Arrays lesen, klassisch und modern
Enthält dein JSON Arrays, musst du über die Elemente laufen, um jeden Wert zu verarbeiten. Delphi kann beides: die klassische indexbasierte Schleife über Items[I] und die modernere for-in-Syntax, die mit jedem TJSONArray funktioniert. For-in ist sauberer, solange du den Index nicht brauchst, die klassische Schleife gibt dir die Position. Bei numerischen Arrays castest du jedes Element auf TJSONNumber, um an Methoden wie AsInt oder AsDouble zu kommen:
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
// String-Array lesen, klassische for-Schleife
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;
// String-Array lesen, moderne for-in-Schleife (ab 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;
// Numerisches Array lesen
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.
Über die Paare eines JSON-Objekts iterieren
Manchmal musst du alle Eigenschaften eines JSON-Objekts verarbeiten, ohne die Schlüsselnamen vorher zu kennen, etwa für einen generischen JSON-Viewer oder wenn die Struktur dynamisch ist. Die for-in-Schleife funktioniert auf TJSONObject genauso wie auf Arrays und liefert TJSONPair-Instanzen. Jedes Paar gibt dir den Schlüssel (über JsonString.Value) und den Wert (über JsonValue), und über ClassName siehst du den Laufzeittyp:
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;
// Mit for-in über alle Paare laufen
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.
JSON-Objekte ändern
JSON-Objekte sind in Delphi voll veränderbar: Du kannst nach dem Erzeugen Eigenschaften hinzufügen, entfernen und aktualisieren. Entscheidend sind die Regeln zur Speicherverwaltung: Rufst du RemovePair auf, geht der Besitz des Paars an dich zurück, du musst es also freigeben. Free darf in Delphi auf nil aufgerufen werden, deshalb funktioniert das Muster RemovePair('key').Free auch dann, wenn es den Schlüssel gar nicht gibt.
Paare hinzufügen und entfernen
Dieses Beispiel zeigt den ganzen Ablauf beim Ändern eines JSON-Objekts: Eigenschaften anlegen, eine entfernen, eine andere aktualisieren. Beachte: Um einen Wert zu aktualisieren, entfernst du zuerst das alte Paar (und gibst es frei) und legst dann ein neues mit demselben Schlüssel an:
program JSONModify;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LJSONObject: TJSONObject;
LRemovedPair: TJSONPair;
begin
LJSONObject := TJSONObject.Create;
try
// Erste Paare anlegen
LJSONObject.AddPair('name', 'Daniele');
LJSONObject.AddPair('city', 'Rome');
LJSONObject.AddPair('temp', 'to be removed');
WriteLn('Initial:');
WriteLn(LJSONObject.ToString);
WriteLn;
// Ein Paar entfernen, RemovePair liefert es zurück (jetzt gehört es dir!)
LRemovedPair := LJSONObject.RemovePair('temp');
LRemovedPair.Free; // auch bei nil sicher, Free prüft Self <> nil
WriteLn('After removing "temp":');
WriteLn(LJSONObject.ToString);
WriteLn;
// Wert aktualisieren: erst entfernen, dann hinzufügen
LRemovedPair := LJSONObject.RemovePair('city');
LRemovedPair.Free;
LJSONObject.AddPair('city', 'Milan');
WriteLn('After updating "city":');
WriteLn(LJSONObject.ToString);
finally
LJSONObject.Free;
end;
ReadLn;
end.
Ausgabe:
Initial:
{"name":"Daniele","city":"Rome","temp":"to be removed"}
After removing "temp":
{"name":"Daniele","city":"Rome"}
After updating "city":
{"name":"Daniele","city":"Milan"}
JSON-Objekte klonen
Wenn du eine geänderte Fassung eines JSON-Objekts brauchst, ohne das Original anzufassen, nimm Clone. Das erzeugt eine tiefe Kopie, einen komplett eigenständigen Objektbaum: Änderungen am Klon wirken sich nicht auf das Original aus und umgekehrt. Genau das brauchst du, wenn du empfangenes JSON umbauen und weiterschicken willst, das Original aber behalten musst:
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 erzeugt eine unabhängige Kopie
LClone := LOriginal.Clone as TJSONObject;
try
// Den Klon ändern, das Original bleibt unberührt
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.
Ausgabe:
Original: {"name":"Daniele","city":"Rome"}
Clone: {"name":"Daniele","city":"Milan"}
Mit JSON-Dateien arbeiten
JSON auf die Platte zu schreiben brauchst du dauernd: Konfigurationsdateien, Caches, Datenexport. Die Unit System.IOUtils bringt die Klasse TFile mit, die Textdateien mit einfachen Methoden liest und schreibt. Das passt perfekt zur String-Darstellung von JSON.
JSON in eine Datei speichern
Um ein JSON-Objekt in eine Datei zu schreiben, wandelst du es mit Format() (lesbare Ausgabe) oder ToString() (kompakte Ausgabe) in einen String um und schreibst diesen auf die Platte. Mit TPath.GetDocumentsPath landet die Datei an einem beschreibbaren Ort, der unter allen Windows-Konfigurationen funktioniert:
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);
// In die Datei speichern
{$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.
JSON aus einer Datei laden
Umgekehrt ist es genauso einfach: Dateiinhalt in einen String lesen, dann mit ParseJSONValue parsen. Prüfe immer zuerst, ob die Datei existiert, damit dir keine Exception um die Ohren fliegt, und ob das Parsen geklappt hat, bevor du an die Daten gehst. Die Pfadnotation funktioniert auf geparstem JSON genauso wie auf selbst gebauten Objekten:
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.
Objekte in JSON verwandeln mit REST.Json
Bis hierher haben wir JSON von Hand gebaut, Paar für Paar. Das ist genau richtig, wenn es auf die Form des Dokuments ankommt. Es ist falsch, wenn du schon eine Klasse hast und sie einfach nur über die Leitung schicken willst.
Dafür liefert Delphi REST.Json mit. Seit XE5 dabei, kein Fremdcode nötig, und die meiste Arbeit erledigt ein einziger Aufruf.
Objekt nach 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));
Ausgabe:
{"id":42,"name":"Daniele Teti","active":true,"address":{"city":"Roma","zipCode":"00100"},"vat_number":"IT01234567890"}
Drei Dinge sind hier passiert.
Das verschachtelte TAddress wurde gleich mitserialisiert, ohne dass du darum gebeten hast. REST.Json durchläuft den Objektgraphen.
InternalNote steht nicht in der Ausgabe. [JSONMarshalled(False)] hält ein Feld aus dem Dokument heraus, und das ist das Attribut, das du auf alles setzen willst, was den Client nichts angeht.
Herausgekommen sind die Schlüssel id, name und zipCode. REST.Json liest die privaten Felder, nicht die Eigenschaften, lässt das F-Präfix weg und macht den ersten Buchstaben klein. Aus FZipCode wird also zipCode, camelCase, ob dir das passt oder nicht. Wenn die Gegenstelle auf einer anderen Schreibweise besteht, überschreibt [JSONName('vat_number')] diese Regel, Feld für Feld.
JSON zurück ins Objekt
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
Beachte, dass im JSON Id und Name mit großem Anfangsbuchstaben stehen und es trotzdem geklappt hat: Beim Lesen spielt Groß- und Kleinschreibung keine Rolle, beim Schreiben schon. Das Objekt kommt fertig gebaut zurück, samt verschachtelter Adresse, und du gibst es frei.
Felder, die im JSON nicht vorkommen, behalten das, was der Konstruktor hinterlassen hat:
LCustomer := TJson.JsonToObject<TCustomer>('{"Name":"Only a name"}');
Id=0 Name=Only a name Active=False
Keine Exception. Wenn eine fehlende Id heißt, dass vorher in der Kette etwas schiefgelaufen ist, musst du selbst danach schauen.
Der Fehler, der dich einen Nachmittag kostet
Deklariere diese Klassen im .dpr, und TJson.JsonToObject scheitert:
EConversionError: Internal: Cannot instantiate type restjson.TCustomer
Der Serializer braucht gelinktes RTTI für die Klasse, und ein Typ, der in der Programmdatei steht, bekommt keins. Verschiebe die Deklarationen in eine Unit, und derselbe Code läuft. Beim Serialisieren aus dem Objekt heraus beschwert sich nichts, deshalb merkst du es erst auf dem Rückweg, meistens nachdem du dich selbst davon überzeugt hast, dass das JSON kaputt ist.
Noch etwas, das nur die neueren Versionen betrifft: TJson.Format ist in Delphi 13 Florence deprecated. Der Compiler sagt dir, was du stattdessen nehmen sollst:
W1000 Symbol 'Format' is deprecated: 'Use TJSONAncestor.Format instead'
Hübsch formatiert gibst du ein Objekt jetzt also so aus:
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": ""
}
Wo REST.Json aufhört
Serialisiere eine Liste, und du stößt an die Grenze:
LArray := TJSONArray.Create;
try
for LCustomer in LList do
LArray.AddElement(TJson.ObjectToJsonObject(LCustomer));
Writeln(LArray.ToJSON);
finally
LArray.Free;
end;
Es funktioniert, und es ist schon eine Schleife, die du selbst schreiben musst. In die andere Richtung, von einem JSON-Array zurück in eine TObjectList<TCustomer>, hat REST.Json überhaupt nichts anzubieten: Du parst das Array selbst und rufst JsonToObject pro Element auf.
REST.Json ist sehr gut darin, ein Objekt nach dem anderen zu serialisieren, mit Namen, die es für dich aussucht. Für alles darüber hinaus brauchst du einen Serializer, der für die Aufgabe gebaut ist.
Wenn es mehr sein muss: die Serializer von DelphiMVCFramework
DelphiMVCFramework bringt einen Serializer mit, den du auch für sich allein benutzen kannst, ganz ohne Server und ohne Controller. Eine Unit und ein Interface.
Eine Liste, in einem Aufruf, in beide Richtungen:
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"}]
Und zurück:
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
Zwei Aufrufe, wo REST.Json dir zwei Schleifen gegeben hat. Beachte außerdem, dass TDateTime als echtes Datum rausgegangen und wieder hereingekommen ist, in ISO 8601 mit Offset. Genau darum geht es im nächsten Abschnitt.
Die Schreibweise der Schlüssel ist hier eine Entscheidung, keine Regel, die dir aufgezwungen wird. Setz das Attribut auf die Klasse, und die ganze Klasse folgt ihm:
[MVCNameCase(ncSnakeCase)]
TSnakeOrder = class
// ...
end;
[MVCNameCase(ncPascalCase)]
TPascalOrder = class
// ...
end;
ncSnakeCase : {"order_id":42,"customer_name":"Daniele Teti"}
ncPascalCase: {"OrderId":42,"CustomerName":"Daniele Teti"}
Das ist der Punkt, der es für die meisten entscheidet. Wenn die API, mit der du reden musst, order_id will, gibt dir REST.Json ein [JSONName] pro Feld, für immer; der Serializer von DelphiMVCFramework gibt dir ein Attribut pro Klasse.
Es steckt allerdings eine Falle darin, und sie macht sich nicht bemerkbar. Die Schreibweise gilt auch beim Lesen. Der Serializer steht per Default auf ncLowerCase, schreibt also placedat und erwartet placedat. Gib ihm dieselbe Payload mit placedAt, und:
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 ist der Nullwert von TDateTime. Kein Fehler, keine Warnung, nur ein Feld, das einfach nicht angekommen ist. Wenn du eine API anbindest, die dir nicht gehört, stell die Schreibweise passend ein und teste eine Payload einmal von Anfang bis Ende, bevor du irgendetwas glaubst.
Dieselbe RTTI-Regel gilt hier übrigens auch. Deklariere TOrder im .dpr, und du bekommst:
Exception: Cannot find RTTI for dmvcser.TOrder. Hint: Is the specified classtype linked in the module?
Anderer Serializer, andere Meldung, dieselbe Ursache: Typen gehören in Units.
Das Datum in JSON und die Stunde, die du verlierst
JSON kennt keinen Datumstyp. Was immer du tust, ein TDateTime verlässt deinen Prozess als String, und alle haben sich auf denselben String geeinigt: ISO 8601. Delphi gibt dir die Umwandlung in System.DateUtils, und es gibt dir eine Voreinstellung, die für den meisten Code falsch ist, den du schreibst.
Die Voreinstellung ist 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
Der zweite Parameter heißt AInputIsUTC und steht per Default auf True. DateToISO8601(SomeDate) erzählt der Welt also, dass der Wert, den du übergeben hast, schon UTC ist. Kommt er von Now, aus einem TDateTimePicker oder aus einer Datenbankspalte, die eine lokale Anwendung geschrieben hat, dann ist er nicht UTC, und du hast gerade ein Z auf eine Ortszeit gestempelt.
Es fliegt keine Exception, und das Dokument ist gültig. Die Stunde ist einfach falsch.
Schreiben und lesen mit demselben 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
Mische die Flags, und der Wert verschiebt sich unbemerkt um deinen Abstand zu UTC:
LText := DateToISO8601(LOriginal, False); // lokal
LBack := ISO8601ToDate(LText); // Default, behandelt ihn als UTC
written with False, read with the default:
15/03/2023 12:00:00 -> 15/03/2023 11:00:00
drift in minutes: 60
Eine Stunde, auf einem Rechner in Italien im März. Im August sind es zwei. Auf einem Rechner, der in UTC läuft, sind es null, und genau deshalb übersteht das die Tests und taucht beim Kunden auf.
In einem Dokument
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
Eingaben, die nicht von dir stammen
ISO8601ToDate wirft eine Exception bei allem, was es nicht lesen kann. Für eine Payload, die über das Netz gekommen ist, nimm die Try-Variante:
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
Dieselbe Form wie TryGetValue weiter oben in diesem Artikel und derselbe Grund, es vorzuziehen.
Entscheide dich einmal für UTC oder Ortszeit, für die ganze Anwendung, und übergib das Flag jedes einzelne Mal explizit. Der Default ist nicht der, den du gemeint hast.
Praxisbeispiel: REST-API-Client
Ein echtes Szenario bringt jetzt alles zusammen: eine REST-API aufrufen und die JSON-Antwort verarbeiten. Dieses Beispiel spricht mit JSONPlaceholder (einer kostenlosen Test-API), holt eine Benutzerliste und parst jeden Eintrag in einen Delphi-Record. Achte darauf, dass hier überall TryGetValue steht, damit fehlende Felder nichts kaputt machen. Bei fremden APIs, die sich jederzeit ändern können, ist das Pflicht.
THTTPClient braucht Delphi XE8 oder neuer.
program JSONRestApiClient;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON,
System.Net.HttpClient; // Braucht Delphi XE8+
type
TUser = record
ID: Integer;
Name: string;
Email: string;
Username: string;
end;
function ParseUser(AJSONObject: TJSONObject): TUser;
begin
// Sicherheitshalber TryGetValue
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;
// Die JSON-Array-Antwort parsen
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.
Praxisbeispiel: Verwaltung einer Konfigurationsdatei
Das letzte Beispiel ist eine vollständige, wiederverwendbare Klasse für die Konfiguration einer Anwendung. TConfigManager kapselt den ganzen Kram rund ums Laden, Speichern und Zugreifen und gibt dir dafür eine saubere, typsichere API. Drin sind Lazy Loading (die Datei wird erst beim ersten Zugriff gelesen), Standardwerte für fehlende Schlüssel und das automatische Anlegen der Datei. Nimm das Muster als Startpunkt für dein eigenes Konfigurationssystem:
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;
// Beispielnutzung
var
Config: TConfigManager;
LConfigFile: string;
begin
LConfigFile := TPath.Combine(TPath.GetDocumentsPath, 'appsettings.json');
WriteLn('Config file: ', LConfigFile);
WriteLn;
Config := TConfigManager.Create(LConfigFile);
try
// Ein paar Werte setzen (die Schlüssel sind flach, keine verschachtelten Objekte)
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;
// Werte wieder auslesen
WriteLn('Database Host: ', Config.GetString('databaseHost'));
WriteLn('Database Port: ', Config.GetInteger('databasePort'));
WriteLn('Logging Enabled: ', Config.GetBoolean('loggingEnabled'));
// Mit Standardwert lesen
WriteLn('Timeout (default 30): ', Config.GetInteger('timeout', 30));
finally
Config.Free;
end;
ReadLn;
end.
Ausgabe:
Config file: C:\Users\yourname\Documents\appsettings.json
Configuration saved!
Database Host: localhost
Database Port: 5432
Logging Enabled: TRUE
Timeout (default 30): 30
JSON-Bibliotheken von Drittanbietern
Der eingebaute JSON-Parser von Delphi reicht für die allermeisten Fälle völlig. In ein paar Szenarien lohnt sich trotzdem ein Blick auf Bibliotheken von Drittanbietern:
| Bibliothek | Am besten für | URL |
|---|---|---|
| JsonDataObjects | hohe Geschwindigkeit, wird von DelphiMVCFramework benutzt | GitHub |
| Grijjy Foundation | großer Funktionsumfang, inklusive BSON | GitHub |
| mORMot2 | Full-Stack-Framework (ORM, SOA, REST), das seine eigene JSON-Schicht mitbringt | GitHub |
Wann eine Drittanbieter-Bibliothek sinnvoll ist
- Große JSON-Dateien (>10MB): Schau dir Streaming-Parser oder JsonDataObjects an
- Sehr häufiges Parsen: JsonDataObjects, weiter unten in diesem Artikel gegen
System.JSONgemessen - BSON wird gebraucht: Grijjy Foundation
- Objektserialisierung:
REST.Jsonfür die einfachen Fälle, die Serializer von DelphiMVCFramework für Listen, Datasets und Kontrolle über die Schreibweise der Schlüssel
Für die meisten Anwendungen reicht das eingebaute System.JSON, und es hat den Vorteil, dass keine externe Abhängigkeit dazukommt.
Wie schnell ist System.JSON wirklich?
“Nimm eine Drittanbieter-Bibliothek, wenn du Performance brauchst” ist leicht geschrieben und schwer umzusetzen. Hier sind Zahlen.
Der Test baut ein Array aus 50.000 Records, jeder mit einem Integer, zwei Strings, einem Boolean, einer Zahl und einem ISO-8601-Zeitstempel: 14 MB UTF-16-Text, die Form eines echten Exports. Dann misst er zwei Aufgaben. Parsen und aus jedem Record einen Integer lesen, das macht ein Client. Und parsen und direkt wieder rausschreiben, das macht ein Proxy.
Vor jedem Lauf steht ein Warm-up, das verworfen wird, und berichtet wird der beste von sieben Läufen. Die Zahlen sind also die Untergrenze und kein Mittelwert über das, was der Rechner sonst noch zu tun hatte.
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, Optimierung an, Bereichs- und Überlaufprüfungen aus, auf einem Core i9-13980HX unter Windows 11.
Also: JsonDataObjects parst rund viermal schneller und schafft den Roundtrip rund zweieinhalbmal schneller. Dieser Abstand ist echt und über die Läufe hinweg stabil.
System.JSON hat die 14 MB trotzdem in etwa einer Sechstelsekunde durchgekaut. Wenn dein JSON ein paar hundert Kilobyte groß ist, und das trifft auf die meisten REST-Antworten und fast jede Konfigurationsdatei zu, wählst du zwischen zwei Millisekunden und einer halben. Das ist keine Entscheidung, das ist ein Rundungsfehler, und System.JSON ist schon installiert.
Greif zu JsonDataObjects, wenn die Payload in Megabyte gemessen wird, oder wenn du in einer Schleife parst, die tausende Male läuft, oder wenn du auf einem Gerät arbeitest, auf dem Rechenzeit nicht umsonst ist. Sonst kostet dich die Abhängigkeit mehr, als sie bringt.
Ein praktischer Hinweis, falls du sie doch reinholst: JsonDataObjects deklariert seine eigenen TJSONObject und TJSONArray. In einer Unit, die beide benutzt, gewinnt das, was in der uses-Klausel zuletzt steht, und du bekommst Fehler, die keinen Sinn ergeben, bis du es siehst:
E2003 Undeclared identifier: 'ParseJSONValue'
E2010 Incompatible types: 'System.JSON.TJSONValue' and 'JsonDataObjects.TJsonArray'
Schreib die Typnamen qualifiziert hin, System.JSON.TJSONObject und JsonDataObjects.TJsonArray, und die Mehrdeutigkeit ist weg.
mORMot2 ist in diesem Vergleich nicht dabei. Es ist ein komplettes Framework und keine JSON-Bibliothek. Es fair zu messen hieße, das ganze Ding reinzuholen und zu konfigurieren, und das ist ein anderer Artikel.
REST-APIs mit JSON bauen
Wenn du in Delphi REST-APIs baust: DelphiMVCFramework hat eine sehr gute JSON-Unterstützung mit automatischer Serialisierung:
[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); // Automatische JSON-Serialisierung
end;
Vollständige Beispiele findest du in den DelphiMVCFramework-Samples, und wenn du die Serializer lieber erklärt bekommst, als sie zu erraten, im offiziellen Guide.
Häufige Fragen
Wie parse ich einen JSON-String in Delphi?
Mit TJSONObject.ParseJSONValue() aus der 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')); // Ausgabe: John
finally
LJSONObject.Free;
end;
end;
end;
Wie gehe ich mit null-Werten im JSON um?
Mit TryGetValue fängst du fehlende und null-Werte gefahrlos ab:
var
LValue: string;
begin
if LJSONObject.TryGetValue<string>('optionalField', LValue) then
WriteLn('Value: ', LValue)
else
WriteLn('Field is missing or null');
end;
Wie iteriere ich über ein JSON-Array?
Mit der modernen for-in-Schleife:
var
LArray: TJSONArray;
LItem: TJSONValue;
begin
if LJSONObject.TryGetValue<TJSONArray>('items', LArray) then
begin
for LItem in LArray do
WriteLn(LItem.Value);
end;
end;
Was ist der Unterschied zwischen Format() und ToString()?
Format(): liefert eingerücktes, für Menschen lesbares JSON (nur ab Delphi 10.3)ToString(): liefert kompaktes JSON ohne Leerraum (besser für die Übertragung im Netz, gibt es in allen Versionen)
Wie ändere ich ein bestehendes JSON-Objekt?
Mit RemovePair und danach AddPair. RemovePair liefert das entfernte Paar zurück (oder nil, wenn es den Schlüssel nicht gibt), es gehört dann dir und du musst es freigeben:
begin
// Remove liefert das Paar zurück, du musst es freigeben!
// Free darf auf nil aufgerufen werden (es prüft intern Self <> nil)
LJSONObject.RemovePair('name').Free;
// Neuen Wert hinzufügen
LJSONObject.AddPair('name', 'New Value');
end;
Welche Delphi-Version hat JSON eingeführt?
- Delphi 2009: erste JSON-Unterstützung in der Unit
DBXJSON - Delphi XE6: umbenannt in
System.JSON, mit Verbesserungen an der API - Delphi 10.1 Berlin: Fluent-API
TJSONObjectBuilder - Delphi 10.3 Rio: Methode
Format()dazu,EJSONParseExceptionmit detaillierten Fehlerinfos
Was ist der Unterschied zwischen GetValue, FindValue und TryGetValue?
| Methode | Liefert | Wenn der Schlüssel fehlt |
|---|---|---|
GetValue<T>('key') |
Wert vom Typ T | wirft eine Exception |
FindValue('key') |
TJSONValue oder nil | liefert nil |
TryGetValue<T>('key', outVar) |
Boolean | liefert False |
Empfehlung: Im Produktivcode TryGetValue nehmen, das ist der sicherste Weg.
Wie erzeuge ich eine tiefe Kopie eines JSON-Objekts?
Mit der Methode Clone:
var
LOriginal, LCopy: TJSONObject;
begin
LOriginal := TJSONObject.ParseJSONValue('{"name":"test"}') as TJSONObject;
try
LCopy := LOriginal.Clone as TJSONObject;
try
// LCopy ist unabhängig, Änderungen wirken sich nicht auf LOriginal aus
finally
LCopy.Free;
end;
finally
LOriginal.Free;
end;
end;
Wie prüfe ich, ob ein JSON-Wert null ist?
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;
Kann ich mit der Pfadnotation auf Array-Elemente zugreifen?
Ja, mit eckigen Klammern und dem Index:
var
LFirstSkill: string;
begin
// Auf das erste Element des skills-Arrays zugreifen
if LJSONObject.TryGetValue<string>('skills[0]', LFirstSkill) then
WriteLn('First skill: ', LFirstSkill);
end;
Wie wandle ich ein Delphi-Objekt in JSON um?
Mit TJson.ObjectToJsonString aus REST.Json, das bei Delphi dabei ist. Es durchläuft den Objektgraphen, liest die privaten Felder und gibt dir camelCase-Schlüssel; [JSONName] benennt ein Feld um, [JSONMarshalled(False)] lässt eines weg. Für Listen, Kontrolle über die Schreibweise und Datasets nimmst du die Serializer von DelphiMVCFramework. Siehe Objekte in JSON verwandeln mit REST.Json weiter oben.
Ist System.JSON schnell genug?
Für fast alles ja. Bei 14 MB JSON, 50.000 Records, parst und liest System.JSON in etwa 158 ms; JsonDataObjects macht dieselbe Arbeit in 42 ms, also rund viermal schneller. Bei einer Payload von ein paar hundert Kilobyte, also bei den meisten REST-Antworten und bei jeder Konfigurationsdatei, ist der Unterschied ein Bruchteil einer Millisekunde. Wechsle die Bibliothek, wenn die Dokumente in Megabyte gemessen werden oder du in einer engen Schleife parst, nicht einfach so. Die Zahlen und die Methode stehen im Abschnitt Wie schnell ist System.JSON wirklich? weiter oben.
Wie serialisiere ich eine TObjectList nach JSON?
REST.Json kann keine Listen: Du gehst sie in einer Schleife durch, rufst TJson.ObjectToJsonObject pro Element auf und hängst jedes an ein TJSONArray. In die andere Richtung hat es gar nichts, also parst du das Array und rufst JsonToObject pro Element auf. Der Serializer von DelphiMVCFramework macht beides in je einem Aufruf, SerializeCollection und DeserializeCollection.
Warum wirft TJson.JsonToObject “Cannot instantiate type”?
Weil die Klasse in der .dpr-Programmdatei deklariert ist, und die bekommt kein gelinktes RTTI. Verschiebe die Typdeklaration in eine Unit, und derselbe Code läuft. Beim Serialisieren beschwert sich nichts, deshalb taucht der Fehler nur auf dem Rückweg auf. Der Serializer von DelphiMVCFramework scheitert aus demselben Grund, nur mit einer anderen Meldung, Cannot find RTTI for ....
Ist System.JSON threadsicher?
Nein, TJSONObject und die verwandten Klassen sind nicht threadsicher. Wenn mehrere Threads auf dasselbe JSON-Objekt zugreifen, musst du selbst synchronisieren (Critical Sections, Locks und so weiter). Für reinen Lesezugriff nach dem Parsen kannst du das Objekt gefahrlos zwischen Threads teilen, solange niemand etwas ändert.
Wie serialisiere ich ein TDateTime nach JSON?
TJSONObject hat keine AddPair-Überladung für TDateTime. Wandle den Wert zuerst in einen ISO-8601-String um und übergib AInputIsUTC explizit, denn der Default ist True und stempelt dir eine Ortszeit als UTC. Siehe Das Datum in JSON und die Stunde, die du verlierst:
LJSONObject.AddPair('createdAt', FormatDateTime('yyyy-mm-dd"T"hh:nn:ss', Now));
Wie groß darf das JSON sein, das Delphi parsen kann?
Es gibt keine harte Grenze, aber System.JSON lädt das ganze Dokument in den Speicher. Bei sehr großen Dateien (>100MB) nimmst du besser Streaming-Parser wie TJsonTextReader aus System.JSON.Readers oder Bibliotheken, die auf große Dokumente ausgelegt sind.
Was ist der Unterschied zwischen System.JSON und DBXJSON?
Es ist dieselbe Bibliothek, nur umbenannt. DBXJSON war der ursprüngliche Unit-Name in Delphi 2009 bis XE5. Ab Delphi XE6 heißt sie System.JSON, passend zu den neuen Namenskonventionen. Die API ist im Wesentlichen gleich geblieben, alten Code umzustellen ist also unspektakulär.
Wie gebe ich JSON hübsch formatiert aus?
Mit der Methode Format() (ab Delphi 10.3), die eingerücktes, lesbares JSON liefert:
WriteLn(LJSONObject.Format()); // Hübsch eingerückt
WriteLn(LJSONObject.ToString); // Kompakt, eine Zeile
In älteren Delphi-Versionen brauchst du dafür eine Fremdbibliothek oder eine eigene Formatierung.
Wie gehe ich mit Sonderzeichen und Unicode im JSON um?
System.JSON kümmert sich beim Erzeugen von JSON selbst um Unicode und escapt Sonderzeichen. Beim Parsen werden Sequenzen wie \n, \t und \uXXXX korrekt zurückgewandelt. Du musst nichts von Hand machen:
LJSONObject.AddPair('message', 'Line 1'#13#10'Line 2'); // Zeilenumbrüche werden automatisch escaped
LJSONObject.AddPair('emoji', '🚀'); // Unicode geht direkt
Wie führe ich zwei JSON-Objekte zusammen?
Eine eingebaute Merge-Funktion gibt es nicht. Lauf über das eine Objekt und häng seine Paare an das andere:
for LPair in LSource do
LTarget.AddPair(LPair.JsonString.Value, LPair.JsonValue.Clone as TJSONValue);
Achtung: Du musst die Werte klonen, denn sie können immer nur zu einem Elternobjekt gehören.
Wie validiere ich JSON vor dem Parsen?
ParseJSONValue liefert bei ungültigem JSON nil, das ist die einfachste Form der Validierung. Für eine Schema-Validierung (Struktur, Pflichtfelder, Typen) brauchst du eine Fremdbibliothek, denn JSON Schema bringt Delphi nicht mit.
Wie komme ich an tief verschachtelte Arrays?
Kombiniere Pfadnotation und Array-Index:
// Zugriff auf: {"data": {"users": [{"name": "Alice"}, {"name": "Bob"}]}}
if LJSONObject.TryGetValue<string>('data.users[1].name', LValue) then
WriteLn(LValue); // Ausgabe: Bob
Kann ich JSON mit FireDAC-Datasets verwenden?
Ja, aber es gibt keine direkte Integration. Entweder du läufst von Hand über das Dataset und baust das JSON, oder du nimmst eine Serialisierungsbibliothek. DelphiMVCFramework und mORMot2 bringen beide die Serialisierung von Datasets nach JSON von Haus aus mit.
Wie gehe ich mit doppelten Schlüsseln im JSON um?
JSON erlaubt doppelte Schlüssel formal, auch wenn davon abgeraten wird. TJSONObject speichert alle Paare, aber GetValue und TryGetValue liefern nur den ersten Treffer. Um an alle Werte mit demselben Schlüssel zu kommen, läufst du mit der for-in-Schleife darüber.
Zusammenfassung
Delphi hat mit der Unit System.JSON eine solide JSON-Unterstützung ab Werk. Das Wichtigste:
- Nimm
TJSONObjectundTJSONArray, um JSON zu erzeugen und zu parsen - Prüfe beim Parsen von JSON-Strings immer auf nil
- Nimm
TryGetValue, um Werte sicher zu lesen, gerade bei optionalen Feldern - Nimm die Pfadnotation (
'parent.child') für verschachtelte Werte - Denk an die Speicherverwaltung: Elternobjekte besitzen ihre Kinder,
RemovePairgibt dir den Besitz zurück - Zieh Fremdbibliotheken erst in Betracht, wenn du sie wegen der Geschwindigkeit wirklich brauchst
Format()für lesbare Ausgabe (ab Delphi 10.3),ToString()für kompakte Ausgabe- Nimm for-in-Schleifen, damit iterierst du sauberer über Arrays und Objektpaare
Wenn du in Delphi moderne REST-APIs bauen willst, schau dir DelphiMVCFramework an: Es hat eine fortgeschrittene JSON-Serialisierung eingebaut und läuft weltweit bei Firmen im Produktivbetrieb.
Verwandte Artikel:
- Delphi-Webanwendungen mit DMVCFramework und TemplatePro bauen - kompletter Leitfaden zur Webentwicklung
- Eine TList von Objekten mit Delphi serialisieren - fortgeschrittene Serialisierungstechniken
- Custom Marshalling/Unmarshalling in Delphi - tiefer Einstieg in eigene JSON-Verarbeitung
Comments
comments powered by Disqus