Open Source  /  radJSON

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.

Delphi RTTI-driven No System.JSON DOM
Status: in active development. The API and configuration surface described below are stabilizing — expect refinement before a tagged release.

Serialize an object in one call

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>

Designed for real Delphi types

Direct streaming

No DOM in the middle

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.

RTL-aware

Collections map naturally

TList / TObjectList become JSON arrays and TDictionary becomes a JSON object out of the box — with configurable handling for enums, GUIDs, dates, and sets.

Strict or lenient

You choose the rigor

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.

One configuration record

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
SerializeEnumsTJsonEnumSerializationEnumToStringserializeEmit an enum as its name or its ordinal. Deserialize auto-detects either form.
DateTimesAreUTCBooleanFalsebothTreat TDateTime/TDate/TTime as UTC for ISO 8601 / Unix conversion.
DateTimeTypeFormatTJsonDateTimeFormatISO8601bothWire format for TDateTime.
DateTypeFormatTJsonDateTimeFormatUnixTimebothWire format for TDate.
TimeTypeFormatTJsonDateTimeFormatUnixTimebothWire format for TTime.
GuidTypeFormatTJsonGUIDFormatUpperWithBracesserializeTGUID output form. Deserialize accepts any casing, braced or bare, dashed or not, plus the 22-char ShortGUID.
NonFiniteFloatsTJsonNonFiniteFloatEmitNullserializeHow a NaN / Infinity float is written (JSON has neither).
EscapeForwardSlashBooleanFalseserializeFalse emits / bare; True emits \/ for embedding in an HTML <script>.
EscapeNonAsciiBooleanTrueserializeTrue escapes chars > 127 as \uXXXX; False emits raw UTF-16. Control chars are always escaped.
MaxDepthInteger64bothNesting-depth guard against cyclic references and deeply nested input; exceeding it raises.
StringKeyedPairsAsObjectBooleanTruebothTArray<TPair<string,V>> as a JSON object {"k":v}; False uses the array-of-pairs form.
DictionaryAsObjectBooleanTruebothTDictionary/TObjectDictionary as a JSON object of its entries; False reflects the object's properties.
ListAsArrayBooleanTruebothTList/TObjectList as a JSON array of its elements; False reflects the object's properties.
UnknownMemberHandlingTJsonUnknownMemberHandlingIgnoredeserializeJSON with no target (unknown key, static-array overflow): Ignore discards it, Reject raises.
ClassMemberVisibilityTMemberVisibilities[mvPublic, mvPublished]bothWhich class member visibilities are (de)serialized. Records always use mvPublic only.
OmitMembersTJsonOmitMembersNeverserializeWhich members to skip: Never / WhenNil / WhenEmpty / WhenDefault — a superset ladder.
UnmappableValueHandlingTJsonUnmappableHandlingRaiseUnmappabledeserializeJSON that cannot be represented in the target (bad enum name, unconstructable class): raise, or fall back leniently.
AllowNonFiniteFloatsBooleanFalsedeserializeParse the non-standard NaN / Infinity / -Infinity tokens some producers emit; default rejects them.
AutoConstructNilObjectsBooleanFalsedeserializeA nil class-typed member: False stays nil; True constructs and populates it (the deserialized object then owns it).
PrettyPrintBooleanFalseserializeEmit indented JSON directly during the write pass (no second reparse). False is compact.
IndentSizeByte2serializeSpaces per indent level when PrettyPrint.
LineBreakTJsonLineBreakCRLFserializeLine 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.

Per-member control with attributes

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;

Custom serializers for your own types

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);

← Back to Open Source