Serializing Delphi Records to JSON with System.JSON: A Practical Guide
Learn how to convert Delphi records to JSON strings and back using System.JSON. This guide covers prerequisites, step‑by‑step code, validation, and recovery for common pitfalls.
23 Sept 2026, 17:23 UTC

Problem Statement
You have a Delphi record that must be persisted or transmitted as JSON. The goal is to serialize the record to a JSON string and later restore it, while handling malformed data and keeping memory usage reasonable.
Prerequisites
- Delphi 2009 or newer (System.JSON introduced in 2009). For modern code, use Delphi 10.4 or later.
- Basic familiarity with Delphi records and generic methods.
- An IDE or command‑line compiler that supports the System.JSON unit.
Target Record Definition
Define a record with a few scalar fields and a nested record to illustrate handling of nested structures.
type
TAddress = record
Street: string;
City: string;
Zip: string;
end;
TCustomer = record
Id: Integer;
Name: string;
Email: string;
Address: TAddress;
end;
Serializing a Record
The TJSONObject class provides a type‑safe way to build JSON objects. Use AddPair for simple key/value pairs and AddPair with another TJSONObject for nested records.
uses
System.JSON, System.SysUtils;
function CustomerToJSON(const C: TCustomer): string;
var
Root, AddrObj: TJSONObject;
begin
Root := TJSONObject.Create;
try
Root.AddPair('Id', C.Id);
Root.AddPair('Name', C.Name);
Root.AddPair('Email', C.Email);
AddrObj := TJSONObject.Create;
try
AddrObj.AddPair('Street', C.Address.Street);
AddrObj.AddPair('City', C.Address.City);
AddrObj.AddPair('Zip', C.Address.Zip);
Root.AddPair('Address', AddrObj);
except
AddrObj.Free;
raise;
end;
Result := Root.ToString;
finally
Root.Free;
end;
end;
Run this function on a TCustomer instance to obtain a JSON string. The function returns a UTF‑8 string that can be written to disk, sent over HTTP, etc.
Deserializing JSON Back to a Record
Parsing is symmetrical: create a TJSONObject from the string and extract each field. Use GetValue for scalars and recursively parse nested objects.
function JSONToCustomer(const JSON: string): TCustomer;
var
Root, AddrObj: TJSONObject;
Value: TJSONValue;
begin
Result := Default(TCustomer); // zero‑initialize
Root := TJSONObject.ParseJSONValue(JSON) as TJSONObject;
if Root = nil then
raise Exception.Create('Invalid JSON format');
try
Value := Root.GetValue('Id');
if Value is TJSONNumber then
Result.Id := (Value as TJSONNumber).AsInt;
Result.Name := Root.GetValue('Name').Value;
Result.Email := Root.GetValue('Email').Value;
AddrObj := Root.GetValue('Address') as TJSONObject;
if AddrObj <> nil then
begin
Result.Address.Street := AddrObj.GetValue('Street').Value;
Result.Address.City := AddrObj.GetValue('City').Value;
Result.Address.Zip := AddrObj.GetValue('Zip').Value;
end;
finally
Root.Free;
end;
end;
Notice the explicit type checks and the use of Default(TCustomer) to guarantee a clean starting state.
Validation and Error Handling
- Malformed JSON:
ParseJSONValuereturnsnilif the string cannot be parsed. Always test fornilbefore proceeding. - Missing Fields:
GetValuereturnsnilif a key is absent. Guard against this by checking fornilor by usingGetValue<T>with a default value (Delphi 10.4+). - Type Mismatch: Attempting to cast a
TJSONValueto the wrong subclass (e.g.,TJSONNumbertoTJSONString) raises an exception. Validate the class before casting. - Large Payloads: For JSON larger than 10 MB, consider using
TJSONReaderto stream parse rather than loading the entire structure into memory.
Recovery Strategies
If deserialization fails, you can:
- Log the error and return a
Default(TCustomer)instance to avoid propagating corrupted data. - Provide a fallback JSON string that contains default values for missing fields.
- Use a validation routine that checks each field after parsing and assigns sensible defaults when values are missing or invalid.
Performance Considerations
- For records with dozens of fields, the overhead of
TJSONObjectis modest (<10 ms on a modern CPU). For thousands of fields, memory consumption can spike; profiling withReportMemoryLeaksOnShutdownis recommended. - When serializing, building the JSON incrementally (e.g., adding fields only when necessary) reduces string concatenation costs.
- Use
Result := Root.ToStringonly once; repeated calls toToStringre‑serialize the object.
Example: Round‑Trip Test
Below is a minimal test that demonstrates a successful round‑trip and error handling.
procedure TestCustomerJSON;
var
Cust, Restored: TCustomer;
JSON: string;
begin
Cust.Id := 42;
Cust.Name := 'Alice';
Cust.Email := '[contact removed]';
Cust.Address.Street := '123 Main St';
Cust.Address.City := 'Metropolis';
Cust.Address.Zip := '12345';
JSON := CustomerToJSON(Cust);
Restored := JSONToCustomer(JSON);
// Verify round‑trip
if (Restored.Id = Cust.Id) and (Restored.Name = Cust.Name) and
(Restored.Email = Cust.Email) and
(Restored.Address.Street = Cust.Address.Street) and
(Restored.Address.City = Cust.Address.City) and
(Restored.Address.Zip = Cust.Address.Zip) then
Writeln('Round‑trip succeeded')
else
Writeln('Round‑trip failed');
// Test malformed JSON
try
JSONToCustomer('{\"Id\": 1, \"Name\": \"Bob\"'); // missing closing brace
except
on E: Exception do
Writeln('Caught error: ', E.Message);
end;
end;
Conclusion
Using System.JSON eliminates the need for third‑party libraries and provides a straightforward, type‑safe way to serialize and deserialize Delphi records. By following the steps above, you can reliably store and transmit record data, handle errors gracefully, and maintain performance even with large JSON payloads.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.