Skip to content

JSON Serialization

MARS maps Delphi values to and from JSON through the helpers in MARS.Core.JSON.pas. In day-to-day use you simply return records/objects/arrays and accept them as [BodyParam] — the content-negotiation layer does the rest. This page covers the conversion rules and how to customize them.

What serializes to what

Delphi typeJSON
string, Charstring
Integer, Int64, Double, Currencynumber
Booleantrue / false
TDateTime / TDate / TTimestring (ISO-8601 by default) or Unix number
enumstring or number
recordobject (one key per field)
class (TObject)object (published/visible properties)
TArray<T> / TObjectList<T>array
TJSONValuepassed through as-is
TDataSet / TFDDataSetarray of objects (see FireDAC)

Nested records, arrays of records, and arrays of objects all serialize recursively.

Direct conversion helpers

When you need to convert explicitly (not through a resource result), use the TJSONObject class helpers:

pascal
uses MARS.Core.JSON;

// record  <->  JSON
var LJson := TJSONObject.RecordToJSON<TPerson>(LPerson, DefaultMARSJSONSerializationOptions);
var LPerson := TJSONObject.JSONToRecord<TPerson>(LJson, DefaultMARSJSONSerializationOptions);

// object  <->  JSON
var LJson := TJSONObject.ObjectToJSON(LCustomer, DefaultMARSJSONSerializationOptions);
var LCustomer := TJSONObject.JSONToObject<TCustomer>(LJson);

// any TValue -> TJSONValue
var LValue := TJSONObject.TValueToJSONValue(TValue.From(LPerson), DefaultMARSJSONSerializationOptions);

Serialization options

TMARSJSONSerializationOptions controls how empty/null values and dates are emitted. There is a global default you can tune once during ignition:

pascal
uses MARS.Core.JSON;

// Include empty/null values in output...
DefaultMARSJSONSerializationOptions.IncludeEmptyOrNullValues;

// ...or strip them all
DefaultMARSJSONSerializationOptions.SkipAllEmptyOrNullValues;

The fields you can set:

FieldEffect
SkipEmptyStringsOmit "" values.
SkipEmptyNumbersOmit zero numbers.
SkipEmptyBooleansOmit false values.
SkipEmptyObjects / SkipEmptyArraysOmit empty {} / [].
SkipNullValuesOmit null.
DateIsUTCTreat TDateTime as UTC.
DateFormatISO8601 (default) or UNIX.
UseDisplayFormatForNumericFieldsUse a field's display format for dataset numbers.

The default is "skip most empty/null values, ISO-8601 UTC dates".

Per-field control attributes

Annotate record/class fields to override the global behavior:

AttributeEffect
[JSONName('customKey')]Serialize the field under a different JSON key.
[JSONSkip]Exclude the field entirely.
[JSONSkipEmptyValues]For this object, omit empty/null members.
[JSONIncludeEmptyValues]For this object, include empty/null members.
pascal
type
  TUser = record
    [JSONName('user_name')] Name: string;
    Email: string;
    [JSONSkip] PasswordHash: string;   // never leaves the server
  end;

[JSONSkipEmptyValues] / [JSONIncludeEmptyValues] can also decorate a resource or method to set the policy for its responses — as the OpenAPI resource does with [JSONSkipEmptyValues].

Custom JSON shapes

If you need a response shape that doesn't match a Delphi type one-to-one, you have three options, in increasing order of control:

  1. Build a TJSONObject/TJSONArray yourself and return it (it passes through unchanged).
  2. Register a custom MessageBodyWriter for your type.
  3. Return a TMARSResponse and write the body directly.
pascal
[GET, Produces(TMediaType.APPLICATION_JSON)]
function Summary: TJSONObject;
begin
  Result := TJSONObject.Create;
  Result.AddPair('count', TJSONNumber.Create(ComputeCount));
  Result.AddPair('generatedAt', DateToISO8601(Now));
end;

YAML

With MARS.YAML.ReadersAndWriters in your ignition uses, methods that [Produces(TMediaType.APPLICATION_YAML)] can emit YAML for the same record/object types — this is how the OpenAPI endpoint serves both JSON and YAML from one method.

Released under the Apache License 2.0.