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 type | JSON |
|---|---|
string, Char | string |
Integer, Int64, Double, Currency | number |
Boolean | true / false |
TDateTime / TDate / TTime | string (ISO-8601 by default) or Unix number |
enum | string or number |
record | object (one key per field) |
class (TObject) | object (published/visible properties) |
TArray<T> / TObjectList<T> | array |
TJSONValue | passed through as-is |
TDataSet / TFDDataSet | array 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:
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);When mapping JSON to an object, a member whose key is not in the JSON keeps its current value: defaults set by the constructor survive, sub-objects the constructor created stay in place (a nested JSON object fills them, it does not replace them), and ToObject on an existing instance merges the keys present into it. Declare a _AssignedValues: TArray<string> field to be told which members actually came from the JSON. Members of a record start zeroed, so the same rule applies.
Serialization options
TMARSJSONSerializationOptions controls how empty/null values and dates are emitted. There is a global default you can tune once during ignition:
uses MARS.Core.JSON;
// Include empty/null values in output...
DefaultMARSJSONSerializationOptions.IncludeEmptyOrNullValues;
// ...or strip them all
DefaultMARSJSONSerializationOptions.SkipAllEmptyOrNullValues;The fields you can set:
| Field | Effect |
|---|---|
SkipEmptyStrings | Omit "" values. |
SkipEmptyNumbers | Omit zero numbers. |
SkipEmptyBooleans | Omit false values. |
SkipEmptyObjects / SkipEmptyArrays | Omit empty {} / []. |
SkipNullValues | Omit null. |
DateIsUTC | Treat TDateTime as UTC. |
DateFormat | ISO8601 (default) or UNIX. |
UseDisplayFormatForNumericFields | Use 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:
| Attribute | Effect |
|---|---|
[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. |
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:
- Build a
TJSONObject/TJSONArrayyourself and return it (it passes through unchanged). - Register a custom MessageBodyWriter for your type.
- Return a
TMARSResponseand write the body directly.
[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.
