JSON Support in Delphi: Complete Guide with Examples (2025)
JSON (JavaScript Object Notation) is the de-facto standard for data interchange in modern applications. Whether you’re building REST APIs, reading configuration files, or communicating with web services, understanding how to work with JSON in Delphi is essential.
This comprehensive guide covers everything you need to know about JSON support in Delphi, with complete, compilable examples you can use in your projects.
TJSONObject, TJSONArray). For streaming/SAX-style parsing, see the System.JSON.Readers and System.JSON.Writers units.
Delphi Version Compatibility
JSON support has evolved significantly across Delphi versions:
| Version | Unit | Key Features |
|---|---|---|
| Delphi 2009 | DBXJSON |
Initial JSON support with basic classes |
| Delphi XE6 | System.JSON |
Unit renamed, improved API |
| Delphi 10.1 Berlin | System.JSON |
TJSONObjectBuilder fluent API, TryGetValue<T> improvements |
| Delphi 10.3 Rio | System.JSON |
Format() method, EJSONParseException with details, performance improvements |
| Delphi 11-12 | System.JSON |
Further optimizations and refinements |
| Delphi 13 Florence | System.JSON |
Latest improvements and continued support |
What is JSON?
JSON is a lightweight, text-based data interchange format. It’s easy for humans to read and write, and easy for machines to parse and generate. A JSON document can contain:
- Objects: Key-value pairs enclosed in curly braces
{} - Arrays: Ordered lists of values enclosed in square brackets
[] - Values: Strings, numbers, booleans (
true/false),null, objects, or arrays
Example JSON structure:
{
"name": "Daniele Teti",
"age": 45,
"active": true,
"skills": ["Delphi", "Python", "SQL"],
"address": {
"city": "Rome",
"country": "Italy"
}
}
Delphi JSON Classes Overview
Delphi provides built-in JSON support through the System.JSON unit. The main classes are:
| Class | Description |
|---|---|
TJSONValue |
Base class for all JSON value types |
TJSONObject |
Represents a JSON object (key-value pairs) |
TJSONArray |
Represents a JSON array (ordered list) |
TJSONString |
Represents a JSON string value |
TJSONNumber |
Represents a JSON numeric value |
TJSONBool |
Represents a JSON boolean value |
TJSONNull |
Represents a JSON null value |
TJSONPair |
Represents a key-value pair in an object |
Creating JSON Objects
Let’s start with the basics: creating JSON objects and adding properties.
Basic JSON Object Creation
The most fundamental operation is creating a TJSONObject and adding key-value pairs to it. Delphi provides convenient AddPair overloads that accept strings, integers, booleans, and doubles directly - no need to wrap primitive values in JSON-specific classes. The following example demonstrates how to build a simple JSON object containing personal information with various data types:
program JSONCreateBasic;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LJSONObject: TJSONObject;
begin
LJSONObject := TJSONObject.Create;
try
// Add string property
LJSONObject.AddPair('firstName', 'Daniele');
LJSONObject.AddPair('lastName', 'Teti');
// Add numeric property (Integer, Int64, Double overloads available)
LJSONObject.AddPair('age', 45);
// Add boolean property
LJSONObject.AddPair('active', True);
// Add null property (no overload - must use TJSONNull)
LJSONObject.AddPair('middleName', TJSONNull.Create);
// Output the JSON
// Note: Format() available since Delphi 10.3 Rio
{$IF CompilerVersion >= 33.0} // Delphi 10.3 Rio
WriteLn(LJSONObject.Format());
{$ELSE}
WriteLn(LJSONObject.ToString);
{$ENDIF}
finally
LJSONObject.Free;
end;
ReadLn;
end.
Output:
{
"firstName": "Daniele",
"lastName": "Teti",
"age": 45,
"active": true,
"middleName": null
}
Creating JSON Arrays
JSON arrays are ordered collections that can hold any combination of values - strings, numbers, booleans, or even other arrays and objects. When you add a TJSONArray to a TJSONObject using AddPair, the parent object takes ownership of the array, so you only need to free the root object. This example shows how to create both a homogeneous array of strings and a mixed-type array:
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');
// Create array of strings
LSkills := TJSONArray.Create;
LJSONObject.AddPair('skills', LSkills);
LSkills.Add('Delphi');
LSkills.Add('Python');
LSkills.Add('SQL');
// Create array with mixed types
LContacts := TJSONArray.Create;
LJSONObject.AddPair('contacts', LContacts);
LContacts.Add('daniele@example.com'); // string
LContacts.Add(123456); // number
LContacts.Add(True); // boolean
{$IF CompilerVersion >= 33.0}
WriteLn(LJSONObject.Format());
{$ELSE}
WriteLn(LJSONObject.ToString);
{$ENDIF}
finally
LJSONObject.Free; // Also frees LContacts and LSkills
end;
ReadLn;
end.
Output:
{
"name": "Daniele Teti",
"skills": [
"Delphi",
"Python",
"SQL"
],
"contacts": [
"daniele@example.com",
123456,
true
]
}
Creating an Array of Objects
One of the most common patterns in real-world JSON is an array containing multiple objects - think of a list of users, products, or any collection of records. Each object in the array can have its own set of properties. When building this structure, you create each object separately and add it to the array using the Add method. The array then owns all the objects you add to it:
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);
// First user
LUser := TJSONObject.Create;
LUsers.Add(LUser);
LUser.AddPair('id', 1);
LUser.AddPair('name', 'Alice');
LUser.AddPair('email', 'alice@example.com');
// Second user
LUser := TJSONObject.Create;
LUsers.Add(LUser);
LUser.AddPair('id', 2);
LUser.AddPair('name', 'Bob');
LUser.AddPair('email', 'bob@example.com');
// Third user
LUser := TJSONObject.Create;
LUsers.Add(LUser);
LUser.AddPair('id', 3);
LUser.AddPair('name', 'Charlie');
LUser.AddPair('email', 'charlie@example.com');
{$IF CompilerVersion >= 33.0}
WriteLn(LRoot.Format());
{$ELSE}
WriteLn(LRoot.ToString);
{$ENDIF}
finally
LRoot.Free;
end;
ReadLn;
end.
Output:
{
"users": [
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
},
{
"id": 2,
"name": "Bob",
"email": "bob@example.com"
},
{
"id": 3,
"name": "Charlie",
"email": "charlie@example.com"
}
]
}
Nested JSON Objects
Complex data often requires hierarchical organization - a person has an address, an address has city and country, and so on. In Delphi, you create nested structures by adding TJSONObject instances as values within other objects. Just like with arrays, the parent object takes ownership of its children, simplifying memory management. This example creates a person with nested address and company objects:
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');
// Create nested address object
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');
// Create another nested object
LCompany := TJSONObject.Create;
LJSONObject.AddPair('company', LCompany);
LCompany.AddPair('name', 'bit Time Professionals');
LCompany.AddPair('website', 'https://www.bittime.it');
{$IF CompilerVersion >= 33.0}
WriteLn(LJSONObject.Format());
{$ELSE}
WriteLn(LJSONObject.ToString);
{$ENDIF}
finally
LJSONObject.Free;
end;
ReadLn;
end.
Output:
{
"name": "Daniele Teti",
"address": {
"street": "Via Roma 123",
"city": "Rome",
"country": "Italy",
"zipCode": "00100"
},
"company": {
"name": "bit Time Professionals",
"website": "https://www.bittime.it"
}
}
Using TJSONObjectBuilder (Delphi 10.1 Berlin+)
If you prefer a more declarative, chainable syntax for building JSON, Delphi 10.1 Berlin introduced TJSONObjectBuilder. This fluent API lets you construct complex JSON structures in a single expression using method chaining with BeginObject, BeginArray, Add, and EndObject/EndArray calls. The builder writes to a TJsonTextWriter, which outputs to a TStringBuilder. While more verbose in setup, this approach produces cleaner, more readable code for complex structures:
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
// Build JSON using fluent API
LBuilder
.BeginObject
.Add('firstName', 'Daniele')
.Add('lastName', 'Teti')
.Add('age', 45)
.Add('active', True)
.BeginObject('address')
.Add('city', 'Rome')
.Add('country', 'Italy')
.EndObject
.BeginArray('skills')
.Add('Delphi')
.Add('Python')
.Add('SQL')
.EndArray
.EndObject;
WriteLn(LStringBuilder.ToString);
finally
LBuilder.Free;
end;
finally
LWriter.Free;
end;
finally
LStringWriter.Free;
end;
finally
LStringBuilder.Free;
end;
ReadLn;
end.
Output:
{
"firstName": "Daniele",
"lastName": "Teti",
"age": 45,
"active": true,
"address": {
"city": "Rome",
"country": "Italy"
},
"skills": [
"Delphi",
"Python",
"SQL"
]
}
Parsing JSON Strings
When you receive JSON data from a web service, file, or any other source, you need to parse it into Delphi objects you can work with. The TJSONObject.ParseJSONValue class method handles this conversion. It returns a TJSONValue (the base class), so you’ll need to check if it’s the expected type - typically TJSONObject or TJSONArray. If the JSON is malformed, the method returns nil, so always check for this before proceeding:
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 returns TJSONValue, cast to appropriate type
LJSONValue := TJSONObject.ParseJSONValue(JSON_STRING);
if LJSONValue = nil then
begin
WriteLn('ERROR: Invalid JSON!');
ReadLn;
Exit;
end;
try
// Check if it's an object (could be array at root level)
if not (LJSONValue is TJSONObject) then
begin
WriteLn('ERROR: Expected JSON object at root level');
Exit;
end;
LJSONObject := TJSONObject(LJSONValue); // Hard cast - safe after "is" check
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 returns nil, which indicates invalid JSON.
Handling Parse Errors (Delphi 10.3+)
When parsing fails, knowing why it failed helps debugging tremendously. Starting from Delphi 10.3 Rio, you can pass True as the second parameter to ParseJSONValue to make it raise an EJSONParseException instead of returning nil. This exception includes the error message, the path where parsing failed, and the character offset - invaluable information when dealing with complex or externally-provided JSON:
program JSONParseErrors;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
INVALID_JSON = '{"name": "Test", "value": }'; // Invalid!
var
LJSONValue: TJSONValue;
begin
{$IF CompilerVersion >= 33.0} // Delphi 10.3 Rio
try
// Use RaiseExc option to get exception with 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}
// Pre-10.3: just check for nil
LJSONValue := TJSONObject.ParseJSONValue(INVALID_JSON);
if LJSONValue = nil then
WriteLn('Invalid JSON - no details available')
else
LJSONValue.Free;
{$ENDIF}
ReadLn;
end.
Reading JSON Values
Delphi provides multiple ways to read values from JSON objects, each with different trade-offs between convenience and safety. Understanding when to use each approach will help you write more robust code that handles missing or unexpected data gracefully.
Method 1: GetValue with Generic Type (Delphi XE7+)
The simplest way to read a value is using the generic GetValue<T> method. You specify the expected type as a type parameter, and Delphi handles the conversion automatically. However, this method raises an exception if the key doesn’t exist, so use it only when you’re certain the key is present:
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> - raises exception if key not found
WriteLn('Name: ', LJSONObject.GetValue<string>('name'));
WriteLn('Age: ', LJSONObject.GetValue<Integer>('age'));
WriteLn('Active: ', LJSONObject.GetValue<Boolean>('active'));
finally
LJSONObject.Free;
end;
ReadLn;
end.
Method 2: TryGetValue - Safe Reading (Recommended)
For production code, TryGetValue<T> is the recommended approach. It returns False if the key is missing or the value cannot be converted to the requested type, allowing you to handle missing data without exception handling. This is particularly useful when parsing JSON from external sources where you can’t guarantee all fields are present:
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 returns False if key not found (no 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');
// This key doesn't exist - no exception raised
if LJSONObject.TryGetValue<string>('middleName', LMiddleName) then
WriteLn('Middle Name: ', LMiddleName)
else
WriteLn('Middle Name: (not specified)');
finally
LJSONObject.Free;
end;
ReadLn;
end.
Method 3: FindValue - Returns nil if Not Found
When you need access to the raw TJSONValue object rather than a converted value, use FindValue. This method returns nil if the key doesn’t exist, never raises an exception, and gives you full access to the JSON value’s properties and methods. It’s useful when you need to check the actual type of a value or when working with complex nested structures:
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 returns nil if not found (never raises 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.
Method 4: Path Notation for Nested Values
One of Delphi’s most convenient features is path notation - you can access deeply nested values using dot-separated paths like 'person.address.city' instead of navigating through multiple intermediate objects. This works with TryGetValue, GetValue, and FindValue, making it much easier to extract specific values from complex JSON structures without writing verbose navigation code:
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
// Use dot notation to access nested values
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.
Reading Arrays - Classic and Modern Approaches
When your JSON contains arrays, you’ll need to iterate over their elements to process each value. Delphi supports both traditional index-based loops using Items[I] and the more modern for-in syntax that works with any TJSONArray. The for-in approach is cleaner when you don’t need the index, while the classic loop gives you access to the position. For numeric arrays, cast each item to TJSONNumber to access methods like AsInt or 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
// Read string array - classic for loop
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;
// Read string array - modern for-in loop (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;
// Read numeric array
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.
Iterating Over JSON Object Pairs
Sometimes you need to process all properties in a JSON object without knowing the key names in advance - for example, when building a generic JSON viewer or when the structure is dynamic. The for-in loop works on TJSONObject just like on arrays, yielding TJSONPair instances. Each pair gives you access to both the key (via JsonString.Value) and the value (via JsonValue), plus you can inspect the runtime type using 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;
// Iterate over all pairs using 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.
Modifying JSON Objects
JSON objects in Delphi are fully mutable - you can add, remove, and update properties after creation. Understanding the memory management rules is crucial: when you call RemovePair, ownership of that pair transfers back to you, so you must free it. The Free method in Delphi is safe to call on nil, so the pattern RemovePair('key').Free works even if the key doesn’t exist.
Adding and Removing Pairs
This example demonstrates the complete lifecycle of modifying a JSON object: creating initial properties, removing one, and updating another. Notice that to update a value, you must remove the old pair first (freeing it) and then add a new one with the same key:
program JSONModify;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LJSONObject: TJSONObject;
LRemovedPair: TJSONPair;
begin
LJSONObject := TJSONObject.Create;
try
// Add initial pairs
LJSONObject.AddPair('name', 'Daniele');
LJSONObject.AddPair('city', 'Rome');
LJSONObject.AddPair('temp', 'to be removed');
WriteLn('Initial:');
WriteLn(LJSONObject.ToString);
WriteLn;
// Remove a pair - RemovePair returns the removed pair (you own it!)
LRemovedPair := LJSONObject.RemovePair('temp');
LRemovedPair.Free; // Safe even if nil - Free checks Self <> nil
WriteLn('After removing "temp":');
WriteLn(LJSONObject.ToString);
WriteLn;
// To update a value: remove then add
LRemovedPair := LJSONObject.RemovePair('city');
LRemovedPair.Free;
LJSONObject.AddPair('city', 'Milan');
WriteLn('After updating "city":');
WriteLn(LJSONObject.ToString);
finally
LJSONObject.Free;
end;
ReadLn;
end.
Output:
Initial:
{"name":"Daniele","city":"Rome","temp":"to be removed"}
After removing "temp":
{"name":"Daniele","city":"Rome"}
After updating "city":
{"name":"Daniele","city":"Milan"}
Cloning JSON Objects
When you need to create a modified version of a JSON object without affecting the original, use the Clone method. This creates a deep copy - a completely independent object tree where changes to the clone don’t affect the original and vice versa. This is essential when you receive JSON data that you need to transform before sending elsewhere while preserving the original:
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 creates an independent copy
LClone := LOriginal.Clone as TJSONObject;
try
// Modify the clone - original is not affected
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.
Output:
Original: {"name":"Daniele","city":"Rome"}
Clone: {"name":"Daniele","city":"Milan"}
Working with JSON Files
Persisting JSON to disk is a common requirement for configuration files, caching, and data export. Delphi’s System.IOUtils unit provides the TFile class with simple methods for reading and writing text files, which pairs perfectly with JSON’s string representation.
Saving JSON to File
To save a JSON object to a file, convert it to a string using Format() (for readable output) or ToString() (for compact output), then write that string to disk. Using TPath.GetDocumentsPath ensures your file goes to a writable location that works across different Windows configurations:
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);
// Save to file
{$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.
Loading JSON from File
Reading JSON from a file is equally straightforward: read the file contents into a string, then parse it with ParseJSONValue. Always check if the file exists first to avoid exceptions, and verify that parsing succeeded before accessing the data. Path notation works just as well on parsed JSON as on manually constructed objects:
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.
Practical Example: REST API Client
Now let’s put everything together in a real-world scenario: calling a REST API and processing the JSON response. This example connects to JSONPlaceholder (a free testing API), fetches a list of users, and parses each one into a Delphi record. Notice how we use TryGetValue throughout to handle potentially missing fields gracefully - a must when dealing with external APIs that might change.
THTTPClient requires Delphi XE8 or later.
program JSONRestApiClient;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON,
System.Net.HttpClient; // Requires Delphi XE8+
type
TUser = record
ID: Integer;
Name: string;
Email: string;
Username: string;
end;
function ParseUser(AJSONObject: TJSONObject): TUser;
begin
// Using TryGetValue for safety
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 JSON array response
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.
Practical Example: Configuration File Manager
This final example shows a complete, reusable class for managing application configuration. The TConfigManager encapsulates all the complexity of loading, saving, and accessing settings while providing a clean, type-safe API. It demonstrates lazy loading (only reads the file when first needed), default values for missing keys, and automatic file creation. You can use this pattern as a starting point for your own configuration systems:
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;
// Demo usage
var
Config: TConfigManager;
LConfigFile: string;
begin
LConfigFile := TPath.Combine(TPath.GetDocumentsPath, 'appsettings.json');
WriteLn('Config file: ', LConfigFile);
WriteLn;
Config := TConfigManager.Create(LConfigFile);
try
// Set some values (keys are flat - not nested objects)
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;
// Read values back
WriteLn('Database Host: ', Config.GetString('databaseHost'));
WriteLn('Database Port: ', Config.GetInteger('databasePort'));
WriteLn('Logging Enabled: ', Config.GetBoolean('loggingEnabled'));
// Read with default value
WriteLn('Timeout (default 30): ', Config.GetInteger('timeout', 30));
finally
Config.Free;
end;
ReadLn;
end.
Output:
Config file: C:\Users\yourname\Documents\appsettings.json
Configuration saved!
Database Host: localhost
Database Port: 5432
Logging Enabled: TRUE
Timeout (default 30): 30
Third-Party JSON Libraries
While Delphi’s built-in JSON parser is excellent for most use cases, some scenarios may benefit from third-party libraries:
| Library | Best For | URL |
|---|---|---|
| JsonDataObjects | High performance, used by DelphiMVCFramework | GitHub |
| Grijjy Foundation | Full-featured, includes BSON support | GitHub |
| mORMot2 | Enterprise-grade, very fast | GitHub |
When to Use Third-Party Libraries
- Large JSON files (>10MB): Consider streaming parsers or JsonDataObjects
- High-frequency parsing: JsonDataObjects or mORMot2 offer better performance
- BSON support needed: Grijjy Foundation
- Object serialization: DelphiMVCFramework’s serializers or mORMot2
For most applications, the built-in System.JSON is sufficient and has the advantage of no external dependencies.
Building REST APIs with JSON
If you’re building REST APIs in Delphi, DelphiMVCFramework provides excellent JSON support with automatic serialization:
[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); // Automatic JSON serialization
end;
See the DelphiMVCFramework samples for complete examples.
Frequently Asked Questions
How do I parse a JSON string in Delphi?
Use TJSONObject.ParseJSONValue() from the System.JSON unit:
uses System.JSON;
var
LJSONObject: TJSONObject;
LValue: TJSONValue;
begin
LValue := TJSONObject.ParseJSONValue('{"name":"John"}');
if (LValue <> nil) and (LValue is TJSONObject) then
begin
LJSONObject := TJSONObject(LValue);
try
WriteLn(LJSONObject.GetValue<string>('name')); // Output: John
finally
LJSONObject.Free;
end;
end;
end;
How do I handle null values in JSON?
Use TryGetValue to safely handle missing or null values:
var
LValue: string;
begin
if LJSONObject.TryGetValue<string>('optionalField', LValue) then
WriteLn('Value: ', LValue)
else
WriteLn('Field is missing or null');
end;
How do I iterate over a JSON array?
Use the modern for-in loop syntax:
var
LArray: TJSONArray;
LItem: TJSONValue;
begin
if LJSONObject.TryGetValue<TJSONArray>('items', LArray) then
begin
for LItem in LArray do
WriteLn(LItem.Value);
end;
end;
What’s the difference between Format() and ToString()?
Format(): Returns indented, human-readable JSON (Delphi 10.3+ only)ToString(): Returns compact JSON without whitespace (better for network transfer, works in all versions)
How do I modify an existing JSON object?
Use RemovePair then AddPair. RemovePair returns the removed pair (or nil if not found) - you own it and must free it:
begin
// Remove returns the pair - you must free it!
// Free is safe to call on nil (it checks Self <> nil internally)
LJSONObject.RemovePair('name').Free;
// Add new value
LJSONObject.AddPair('name', 'New Value');
end;
Which Delphi version introduced JSON support?
- Delphi 2009: Initial JSON support in
DBXJSONunit - Delphi XE6: Renamed to
System.JSONwith API improvements - Delphi 10.1 Berlin:
TJSONObjectBuilderfluent API - Delphi 10.3 Rio: Added
Format()method,EJSONParseExceptionwith detailed error info
What is the difference between GetValue, FindValue, and TryGetValue?
| Method | Returns | On Key Not Found |
|---|---|---|
GetValue<T>('key') |
Value of type T | Raises exception |
FindValue('key') |
TJSONValue or nil | Returns nil |
TryGetValue<T>('key', outVar) |
Boolean | Returns False |
Recommendation: Use TryGetValue for production code as it’s the safest approach.
How do I create a deep copy of a JSON object?
Use the Clone method:
var
LOriginal, LCopy: TJSONObject;
begin
LOriginal := TJSONObject.ParseJSONValue('{"name":"test"}') as TJSONObject;
try
LCopy := LOriginal.Clone as TJSONObject;
try
// LCopy is independent - modifications don't affect LOriginal
finally
LCopy.Free;
end;
finally
LOriginal.Free;
end;
end;
How do I check if a JSON value is 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;
Can I use path notation to access array elements?
Yes, use bracket notation with the index:
var
LFirstSkill: string;
begin
// Access first element of skills array
if LJSONObject.TryGetValue<string>('skills[0]', LFirstSkill) then
WriteLn('First skill: ', LFirstSkill);
end;
How do I convert a Delphi object to JSON?
For simple cases, build the JSON manually with TJSONObject. For automatic serialization of objects and records, use DelphiMVCFramework’s serializers or third-party libraries like mORMot2. These can convert any Delphi object to JSON with a single method call.
Is System.JSON thread-safe?
No, TJSONObject and related classes are not thread-safe. If multiple threads need to access the same JSON object, you must implement your own synchronization (critical sections, locks, etc.). For read-only access after initial parsing, you can safely share the object across threads as long as no modifications occur.
How do I serialize a TDateTime to JSON?
TJSONObject doesn’t have a built-in AddPair overload for TDateTime. Convert it to a string first using a standard format like ISO 8601:
LJSONObject.AddPair('createdAt', FormatDateTime('yyyy-mm-dd"T"hh:nn:ss', Now));
What’s the maximum JSON size Delphi can parse?
There’s no hard limit, but System.JSON loads the entire document into memory. For very large files (>100MB), consider streaming parsers like TJsonTextReader from System.JSON.Readers, or third-party libraries optimized for large documents.
What is the difference between System.JSON and DBXJSON?
They are the same library - just renamed. DBXJSON was the original unit name in Delphi 2009-XE5. Starting with Delphi XE6, it was renamed to System.JSON to follow the new naming conventions. The API is essentially the same, so migrating old code is straightforward.
How do I pretty print JSON in Delphi?
Use the Format() method (Delphi 10.3+) which returns indented, human-readable JSON:
WriteLn(LJSONObject.Format()); // Pretty printed with indentation
WriteLn(LJSONObject.ToString); // Compact, single line
For older Delphi versions, use third-party libraries or implement custom formatting.
How do I handle special characters and Unicode in JSON?
System.JSON automatically handles Unicode and escapes special characters when generating JSON. When parsing, escaped sequences like \n, \t, and \uXXXX are correctly converted. No manual handling is needed:
LJSONObject.AddPair('message', 'Line 1'#13#10'Line 2'); // Newlines auto-escaped
LJSONObject.AddPair('emoji', '🚀'); // Unicode works directly
How do I merge two JSON objects?
There’s no built-in merge function. Iterate over one object and add its pairs to the other:
for LPair in LSource do
LTarget.AddPair(LPair.JsonString.Value, LPair.JsonValue.Clone as TJSONValue);
Note: You must clone the values since they can only belong to one parent object.
How do I validate JSON before parsing?
ParseJSONValue returns nil for invalid JSON, which serves as basic validation. For schema validation (checking structure, required fields, types), you’ll need third-party libraries as Delphi doesn’t include built-in JSON Schema support.
How do I access deeply nested arrays?
Combine path notation with array indexing:
// Access: {"data": {"users": [{"name": "Alice"}, {"name": "Bob"}]}}
if LJSONObject.TryGetValue<string>('data.users[1].name', LValue) then
WriteLn(LValue); // Output: Bob
Can I use JSON with FireDAC datasets?
Yes, but there’s no direct integration. You can manually iterate over a dataset and build JSON, or use serialization libraries. DelphiMVCFramework and mORMot2 both provide dataset-to-JSON serialization out of the box.
How do I handle JSON with duplicate keys?
JSON technically allows duplicate keys, though it’s discouraged. TJSONObject stores all pairs, but GetValue/TryGetValue only return the first match. To access all values with the same key, iterate using the for-in loop.
Summary
Delphi provides robust, built-in JSON support through the System.JSON unit. Key takeaways:
- Use
TJSONObjectandTJSONArrayfor creating and parsing JSON - Always check for nil when parsing JSON strings
- Use
TryGetValuefor safe value reading with optional fields - Use path notation (
'parent.child') for nested values - Remember memory management: parent objects own their children;
RemovePairreturns ownership to you - Consider third-party libraries only for specific performance needs
- Use
Format()for readable output (Delphi 10.3+),ToString()for compact output - Use for-in loops for cleaner iteration over arrays and object pairs
For building modern REST APIs in Delphi, check out DelphiMVCFramework - it includes advanced JSON serialization and is used in production by companies worldwide.
Comments
comments powered by Disqus