Open Source / radJSON
An object- and record-oriented JSON serializer and deserializer
for Delphi. It streams straight to and from text through RTTI — with no
intermediate System.JSON DOM to build, walk, and free — and
every behavior is controlled by a single configuration record.
The primary API is two class types. Serialize objects, records,
or dynamic arrays; deserialize the same shapes plus a top-level scalar. An
optional class helper adds ToJSON / FromJSON straight
onto every object for convenience.
uses radJSON.Serializer, radJSON.Deserializer;
JSON := TJSONSerializer.ObjectToJSON(obj); // also RecordToJSON<T> / ArrayToJSON<T>
TJSONDeserializer.DeserializeObject(JSON, obj); // also DeserializeRecord / Array / Value<T>
Values are read and written straight against the text, so there is no intermediate document object to allocate and tear down on the hot path.
TList / TObjectList become JSON arrays and
TDictionary becomes a JSON object out of the box — with
configurable handling for enums, GUIDs, dates, and sets.
Reject unknown members and unmappable values for a strict contract, or fall back leniently — the deserializer never silently misrepresents a value unless you ask it to.
All behavior lives in a single TJsonConfig record.
Start from the default, change only the fields you care about, and pass it to
any entry point — or install one process-wide default at startup. Options
cover pretty-printing, enum and GUID formats, date/time wire formats, member
visibility, omitting empty or default members, non-finite floats, depth
guarding, and unknown- / unmappable-member handling.
Customize only what you need
uses radJSON.Config, radJSON.Serializer, radJSON.Deserializer;
var
cfg: TJsonConfig;
begin
cfg := TJsonConfig.CreateDefaultConfig;
cfg.PrettyPrint := True; // indented output
cfg.EscapeNonAscii := False; // emit raw UTF-16
cfg.UnknownMemberHandling := TJsonUnknownMemberHandling.Reject;
JSON := TJSONSerializer.ObjectToJSON(obj, cfg);
TJSONDeserializer.DeserializeObject(JSON, obj, cfg);
end;
Every option in radJSON.Config
| Option | Type | Default | Applies to | Purpose |
|---|---|---|---|---|
| SerializeEnums | TJsonEnumSerialization | EnumToString | serialize | Emit an enum as its name or its ordinal. Deserialize auto-detects either form. |
| DateTimesAreUTC | Boolean | False | both | Treat TDateTime/TDate/TTime as UTC for ISO 8601 / Unix conversion. |
| DateTimeTypeFormat | TJsonDateTimeFormat | ISO8601 | both | Wire format for TDateTime. |
| DateTypeFormat | TJsonDateTimeFormat | UnixTime | both | Wire format for TDate. |
| TimeTypeFormat | TJsonDateTimeFormat | UnixTime | both | Wire format for TTime. |
| GuidTypeFormat | TJsonGUIDFormat | UpperWithBraces | serialize | TGUID output form. Deserialize accepts any casing, braced or bare, dashed or not, plus the 22-char ShortGUID. |
| NonFiniteFloats | TJsonNonFiniteFloat | EmitNull | serialize | How a NaN / Infinity float is written (JSON has neither). |
| EscapeForwardSlash | Boolean | False | serialize | False emits / bare; True emits \/ for embedding in an HTML <script>. |
| EscapeNonAscii | Boolean | True | serialize | True escapes chars > 127 as \uXXXX; False emits raw UTF-16. Control chars are always escaped. |
| MaxDepth | Integer | 64 | both | Nesting-depth guard against cyclic references and deeply nested input; exceeding it raises. |
| StringKeyedPairsAsObject | Boolean | True | both | TArray<TPair<string,V>> as a JSON object {"k":v}; False uses the array-of-pairs form. |
| DictionaryAsObject | Boolean | True | both | TDictionary/TObjectDictionary as a JSON object of its entries; False reflects the object's properties. |
| ListAsArray | Boolean | True | both | TList/TObjectList as a JSON array of its elements; False reflects the object's properties. |
| UnknownMemberHandling | TJsonUnknownMemberHandling | Ignore | deserialize | JSON with no target (unknown key, static-array overflow): Ignore discards it, Reject raises. |
| ClassMemberVisibility | TMemberVisibilities | [mvPublic, mvPublished] | both | Which class member visibilities are (de)serialized. Records always use mvPublic only. |
| OmitMembers | TJsonOmitMembers | Never | serialize | Which members to skip: Never / WhenNil / WhenEmpty / WhenDefault — a superset ladder. |
| UnmappableValueHandling | TJsonUnmappableHandling | RaiseUnmappable | deserialize | JSON that cannot be represented in the target (bad enum name, unconstructable class): raise, or fall back leniently. |
| AllowNonFiniteFloats | Boolean | False | deserialize | Parse the non-standard NaN / Infinity / -Infinity tokens some producers emit; default rejects them. |
| AutoConstructNilObjects | Boolean | False | deserialize | A nil class-typed member: False stays nil; True constructs and populates it (the deserialized object then owns it). |
| PrettyPrint | Boolean | False | serialize | Emit indented JSON directly during the write pass (no second reparse). False is compact. |
| IndentSize | Byte | 2 | serialize | Spaces per indent level when PrettyPrint. |
| LineBreak | TJsonLineBreak | CRLF | serialize | Line ending when PrettyPrint: CRLF or LF. |
Enum values —
TJsonEnumSerialization: EnumToString, EnumToValue.
TJsonDateTimeFormat: ISO8601, UnixTime, DelphiDateTime, DelphiDate, DelphiTime.
TJsonGUIDFormat: UpperWithBraces, LowerWithBraces, UpperNoBraces, LowerNoBraces, UpperNoDashes, LowerNoDashes, ShortGUID.
TJsonNonFiniteFloat: EmitNull, EmitZero, RaiseError.
TJsonUnknownMemberHandling: Ignore, Reject.
TJsonUnmappableHandling: RaiseUnmappable, BestEffort.
TJsonOmitMembers: Never, WhenNil, WhenEmpty, WhenDefault.
TJsonLineBreak: CRLF, LF.
Where the config record sets document-wide policy, custom
attributes in radJSON.CustomAttributes tune individual members
— decorate a field or property and the (de)serializer honors it, taking
precedence over the global config for that member. Skip a member entirely with
[JsonIgnore]; rename it on the wire with
[JsonCustomName('id')]; override a date member's format with
[JsonDateFormat(TJsonDateTimeFormat.UnixTime)] or an enum's
name-vs-ordinal choice with [JsonEnum(TJsonEnumSerialization.EnumToValue)];
force a numeric member onto the wire as a quoted string with
[JsonNumberAsString]; carry an opaque raw-JSON passthrough in a
string member with [JsonRawValue]; and emit a
TStrings member as a {name:value} map with
[JsonSerializeAsStringMap]. Because Delphi cannot attribute
individual enum values, two type-level attributes,
[JsonEnumNames('active,inactive,pending')] and
[JsonEnumIntValues('1,2,5')], map an enum's members to custom
string or integer wire values.
Attributes on a class
TAccount = class
[JsonIgnore]
property CacheKey: string read fCacheKey;
[JsonCustomName('id')]
property AccountId: Int64 read fAccountId write fAccountId;
[JsonDateFormat(TJsonDateTimeFormat.UnixTime)]
property Created: TDateTime read fCreated write fCreated;
end;
When a type needs wire handling that reflection alone cannot
express, register a pair of procedures against its TypeInfo with
gRttiCache.AddCustomSerializer /
AddCustomDeserializer. Each receives the member, an instance
pointer, and an IJsonWriter or IJsonReader, so you
write and read the exact wire shape you want — radJSON then uses your
procedures anywhere that type appears. The radJSON.CustomSerializers
unit ships built-in handlers for the common
TPair<string, V> value types (string, Integer, Int64, Single,
Double, Extended, Boolean), and exposes a generic
TCustomSerializers.SerializeStringKeyedPairs<V> /
DeserializeStringKeyedPairs<V> helper that maps a
TArray<TPair<string, V>> of record V to
a JSON object — the entry point the OpenAPI / AWS model code generators
delegate to for their nested-object maps.
Register a per-type serializer
procedure SerializePaths(const M: TMember; I: Pointer; const W: IJsonWriter);
begin
TCustomSerializers.SerializeStringKeyedPairs<TPathItem>(M, I, W);
end;
gRttiCache.AddCustomSerializer(
TypeInfo(TArray<TPair<string, TPathItem>>), SerializePaths);