JSON Format
XData server uses JSON format in message payloads when receiving and sending HTTP messages to represent several different structures like entities, collection of entities or individual properties. Although JSON specification is very simple and describes completely how to use JSON format, the meaning of each JSON structure (especially name/value pairs) depend on the application and server behavior.
The following topics describes how each different structure is represented in JSON format by XData, and additional useful info about it.
Entity and Object Representation
Any Aurelius entity or simple Delphi object is serialized as a JSON object. Each property is represented as a name/value pair within the object.
The name of the properties in JSON for a simple Delphi object will be the field names of the object class, with the leading "F" removed from the name, if it exists.
The name of the properties in JSON for an Aurelius entity will be the same as property names defined in the XData Model according to Aurelius Equivalence to Entity Model.
A Delphi object in payload will always have all properties, unless you explicitly change this behavior by using JSON attributes to customize the serialization.
An Aurelius entity in a payload may be a complete entity, with all existing properties for that entity type, or a partial entity update (for partial update operations), which do not list all properties of the entity.
The following text illustrates how XData represents a simple Customer object (TCustomer class).
{
"$id": 1,
"Id": 55,
"Name": "Joseph",
"Birthday": "1980-05-20",
"Sex": "tsMale",
"Picture": null
}
In above JSON representation, Customer entity type contains simple properties Id (integer), Name (string), Birthday (date), Sex (enumeration) and Picture (blob). The property values are represented as direct JSON values. Note that XData also includes some metadata information in the JSON object, like "$id" name which represents the object reference id. In some cases, XData might include the "xdata.type" annotation which is needed for it to work properly with polymorphism. The following topics describe more specific details about how entities and its properties are represented in XData.
Property Values
Simple (scalar) properties of an entity/object are represented in as name/value pairs in the JSON object. The JSON name contains the property name, and value contains a JSON value that can be either a JSON string, number, boolean or null, depending on the property type. The format of most types are very straightforward (a string property is represented as a string JSON, an integer property as integer JSON, and so on), but a few types have some specific representation. The following table explains the JSON representation of the most common property types.
Data Type | Examples | Description |
---|---|---|
<null values> | null | Represented as JSON null literal. |
Binary | "T0RhdGE" | Represented as JSON string, whose content must be the binary value encoding as Base64. |
Boolean | true false |
Represented as JSON true or false literals. |
DateTime | "2013-12-25" "2013-12-25T12:12" "2013-12-25T12:12:20.050" |
Represented as JSON string, whose content must be the date/time in ISO 8601 format (YYYY-MM-DDTdd:mm:ss.zzz). The time part can be completely omitted. If time part is present, hour and minutes are required, and seconds and milliseconds parts can also optionally be omitted. |
Enum types | "tsMale" "Yellow" |
Represented as JSON string, whose content is the name corresponding to the ordinal value of the enumerated property. |
Float | 3.14 1.2e-5 |
Represented as JSON number. |
Guid | "E314E4B3-ECE5-4BD5-9D41-65B7E74F7CC8" | Represented as JSON string, whose content must be the string representation of the GUID, must not have enclosing brackets and must have the hyphens separating the five guid blocks (8 char, 4 char, 4 char, 4 char, 12 char). Each guid block is composed by hexadecimal digits. |
Integer | 1234 | Represented as a JSON number. |
String | "John" | Represented as JSON string, using JSON string escaping rules. |
Object References
XData provides the concept of object referencing in JSON. This is useful to indicate which objects are the same object instance, and also to avoid circular references.
When serializing objects, XData attributes an "instance id" for that object and serializes it. The instance id is serialized as the first property of the object, with the name "$id". If during serialization the serializer finds another reference to the same object, it won't serialize the object again, but instead, will create a "instance reference" that refers to the instance id of the object previously serialized.
For example, consider you have Product entity type which has a Category property that points to a Category entity type. Suppose you have a list of two Product entities "Ball" and "Doll" that point to the same "Toys" category. This is how such list would be serialized:
[
{
"$id": 1,
"@xdata.type": "XData.Default.Product",
"Id": 10,
"Name": "Ball",
"Category": {
"$id": 2,
"@xdata.type": "XData.Default.Category",
"Id": 5,
"Name": "Toys"
}
},
{
"$id": 2,
"@xdata.type": "XData.Default.Product",
"Id": 12,
"Name": "Doll",
"Category": {
"$ref": 2,
}
}
]
The TXData
In the JSON example above, when creating the second Product instance and setting the Category property, the deserializer will not create a new Category object. Instead, it will use the same Category object created in the first product. Thus, the Category property of both Product objects will point to the same Category object instance.
If the deserializer can't find an instance pointed by the "$ref" property, an error will be raised. All other properties of a JSON object containing a "$ref" property will be ignored.
Although the XData serializers adds an "$id" property to all objects, such property is not required in XData notation, thus the deserializer won't raise an error if an object doesn't have an instance id. But if present, this property must have the very first property (name/value pair) in the JSON object.
The rule of when object reference ($ref) will be used depend on the
TXData
[
{
"$id": 1,
"@xdata.type": "XData.Default.Product",
"Id": 10,
"Name": "Ball",
"Category": {
"$id": 2,
"@xdata.type": "XData.Default.Category",
"Id": 5,
"Name": "Toys"
}
},
{
"$id": 2,
"@xdata.type": "XData.Default.Product",
"Id": 12,
"Name": "Doll",
"Category": {
"$id": 2,
"@xdata.type": "XData.Default.Category",
"Id": 5,
"Name": "Toys"
}
}
]
Annotation "xdata.type"
All JSON objects in XData that represent an object might contain a metadata property named "@xdata.type". If present it must appear before any regular (non-metadata) property, otherwise an error is raised while deserializing the object. This property indicates the entity type of the JSON object. This is used by the deserializer to know which class to be used to instantiate the object. The value of this property is the name of the object class, or Aurelius entity type, prefixed by "XData.Default".
An example of an object of type "Customer":
{
"$id": 1,
"@xdata.type": "XData.Default.Customer",
"Id": 55,
"Name": "Joseph"
}
When sending requests to the server, "@xdata.type" annotation is optional. If not present, XData will try to infer the entity type from the context - for example, if your service operation is expecting a parameter of type TCustomer then it will deserialize the JSON as a TCustomer instance, if xdata.type annotation is missing. Thus, this annotation is mostly used when dealing with polymorphism, so you be sure that the entity will be deserialized as the correct type.
The presence of xdata.type annotation in server responses depend on the
configuration of TXData
Representing Associated Objects
There are several ways to represent an associated entity in a JSON object.
An associated object is any object property that references a regular Delphi object.
An associated entity is any object property that references a TMS Aurelius entity through a navigation property (association).
For example, you might have an Aurelius entity type Customer with a navigation property Country, which target is the Country entity type. So the Country object is associated with the Customer object through the Country property, and might be represented in JSON notation. The equivalent Aurelius class would be something like this (parts of code removed):
TCustomer = class
{ code stripped }
property Country: TCountry;
end;
Here we describe the different ways to represent an associated entity/object in JSON format. Please note that this is not related to representing object references, they are different concepts.
XData server responses will often use entity references when responding
to resource requests. This behavior might be affected by the
XData-Expand
Note that all the following options are only available for associated entities. For associated objects, only inline representation is allowed.
Entity Reference
This only applies to associated entities.
This is the most common way to represent an associated entity and should be preferred over any other method, if possible. It's somehow related to reference to an object instance, in a programming language. It's represented by annotating the navigation property name with "xdata.ref" sufix. The value of such property must be the canonical id of the associated object:
"Country@xdata.ref": "Country(10)"
In the simplified example above, the property Country is associated to the Country object with id equals to 10.
Clients can use that value to retrieve further info about the associated object by using the rules described in "canonical id" topic, performing an extra request to the server. Also, in update/insert operations, clients can also use this format to tell the server how to update the association. In the example above, if you send such object to the server in an update operation, the customer object will have its Country property updated to point to the Country(10) object (country with id equals to 10).
Entity/Object Inline
In some specific situations, the associated entity might be represented inline (for associated objects, this is always the case). That would be represented as a JSON object representing the whole object inline:
{
"Country": {
"$id": 2,
"@xdata.type": "XData.Default.Country",
"Id": 10,
"Name": "Germany",
}
}
Such representation would usually be used by client applications that want to provide associated entities that still do not have an id, thus can't be represented using the association reference format. Or can be returned by the server, if the expand level is increased. You can also explicitly ask for this format using the $expand query option.
To illustrate how association references are different from object references, you can have an entity represented inline, but using an object reference. For example, if the country object represented in the previous code (country of id 10 represented inline) was already present in the JSON tree before, it could be represented as an object reference:
{
"Country": {
"$ref": 2
}
}
Proxy Info
This only applies to associated entities.
For performance reasons, the server might provide an object representation using proxy info instead of an association reference. The format is similar to the association reference, but the annotation is "xdata.proxy". The value must contain an URL (relative to server base URL) that clients can use to retrieve the associated object using a GET request:
"Country@xdata.proxy": "$proxy?id=52&classname='Customer'&classmember='Country'"
Association reference and proxy info are very similar, but they have two differences:
An association reference value has a very specific format (canonical id). Thus, association references, in addition to be a valid relative URL to retrieve the associated object, it is also in a specific format so that entity type and id can be parsed from the value, allowing you to safely know the entity type and id of the associated object without performing a GET request. Proxy info, in turn, is just a relative URL to retrieve the object, but the URL format is not standard and no additional metadata info can be safely extracted from the URL.
On data modification requests (insert/update) operations, association references are used to update the value of the navigation property (the object associated with the main object). Proxy info, in turn, are completely ignored by the server and do not cause any modification in the navigation property.
Blob Representation
In general, binary values are represented in JSON notation as base64 value, following the rules for serializing simple property values. The following JSON name/value pair is an example of a binary property Data representation:
"Data": "T0RhdGE"
When the property type in Aurelius is declared as TBlob type, though, the name/value pair in JSON notation might be declared using the "xdata.proxy" annotation after the property name, which allows clients to load the blob content in a lazy (deferred) way. The value of such name/value pair is the URL used to retrieve the blob content:
"Data@xdata.proxy": "Customer(55)/Data"
If the property is a TBlob type, the property will always be represented like that, unless:
1. The object to which the property belongs is a transient object (in the example above, if the customer object didn't have an id). In this case, the blob content will be represented inline as base64 string.
2. The blob content is somehow available to the server when the object is retrieved from database, and the blob content is empty. In this case, the property will be serialized normally (without annotation), and value is "null":
"Data": null
3. If the $expand query option was used to explicit load the content of the blob property. In this case, the blob content will be represented inline as base64 string.
Including Or Excluding Properties
This is how XData decides what properties to serialize/deserialize in JSON. When we mention "serialize" here it also implies "deserialize" (meaning that the JSON property will be recognized as valid and associated field/property will be updated from the JSON value).
For entities, all class members (fields or properites) that are mapped using Aurelius will be serialized. Any transient field or property will not be serialized.
For objects, all class fields will be serialized. No property will be serialized.
You can override this default and choose, for both your entity and object classes, what property will be included in JSON. The way to do that is different for entities and objects.
Aurelius Entities
For entities you can use attributes XDataProperty and
XDataExcludeProperty (declared in unit XData.Model.Attributes
).
They have no arguments, and when put in a class member (field or property) it
imply indicates that such property will be added or removed from the JSON.
In the following example, even though FBirthday is the field being mapped to the database, the final JSON will not include it, but instead will have three properties: Year, Month and Day.
uses {...}, XData.Model.Attributes;
[Entity, Automapping]
TCustomer = class
private
FId: Integer;
FName: string;
[XDataExcludeProperty]
FBirthday: TDateTime;
function GetDay: Integer;
function GetMonth: Integer;
function GetYear: Integer;
procedure SetDay(const Value: Integer);
procedure SetMonth(const Value: Integer);
procedure SetYear(const Value: Integer);
public
property Id: Integer read FId write FId;
property Name: string read FName write FName;
property Birthday: TDateTime read FBirthday write FBirthday;
[XDataProperty]
property Year: Integer read GetYear write SetYear;
[XDataProperty]
property Month: Integer read GetMonth write SetMonth;
[XDataProperty]
property Day: Integer read GetDay write SetDay;
end;
PODO (Plain Old Delphi Objects)
JsonProperty and JsonIgnore attributes
For regular objects, you can use similar approach, but using attributes
JsonProperty and JsonIgnore (declared in unit Bcl.Json.Attributes
).
One small difference is that JsonProperty can optionally receive a string
which is the name of the property in final JSON.
In the following example, final Json will have properties Id, PersonName, Birthday and YearOfBirth. Field FTransient will not be serialized because of attribute JsonIgnore. Field FName will be serialized with a different name (PersonName) and property YearOfBirth will also be serialized because of the presence of attribute JsonProperty.
uses {...}, Bcl.Json.Attributes
TDTOPerson = class
private
FId: Integer;
[JsonProperty('PersonName')]
FName: string;
FBirthday: TDateTime;
[JsonIgnore]
FTransient: string;
public
property Id: Integer read FId write FId;
property Name: string read FName write FName;
[JsonProperty]
property YearOfBirth: Integer read GetYearOfBirth;
property Birthday: TDateTime read FBirthday write FBirthday;
property Transient: string read FTransient write FTransient;
end;
JsonInclude attribute
You can also use JsonInclude attribute to specify which properties will be serialized to JSON based on their values. JsonInclude attribute should be added to the class type and receive a parameter of type TInclusionMode:
[JsonInclude(TInclusionMode.NonDefault)]
TDTOAddress = class
private
{...}
Valid TInclusionMode values are:
TInclusionMode.Always: Always serialize all DTO fields/properties (default behavior).
TInclusionMode.NonDefault: Only serialize the fields/property if the value is a non-default value (not "empty", so to speak). Here is the list of checked types and what is considered their default value:
- Object types: Nil pointer;
- String types: Empty string;
- Numeric types: Zero;
- Enumerated Types: First enumerated value (ordinal valueof zero);
- Set Types: Empty set;
- Array Types: Empty array.
JsonEnumValues attribute
When serializing an enumerated value, by default it's the enumerated name value that will be serialized. For example:
type
TMyEnum = (myFirst, mySecond, myThird);
A property of type TMyEnum could be serialized as following:
"MyEnumProp": "myFirst"
You can change that value using the JsonEnumValues property, passing the new values in a comma-separated string:
type
[JsonEnumValues('first,second,third')]
TMyEnum = (myFirst, mySecond, myThird);
That will generate the following JSON:
"MyEnumProp": "first"
JsonNamingStrategy attribute
If you have a general rule for naming the properties in the final JSON, you can use JsonNamingStrategy attribute instead of using a JsonProperty attribute for every single field/property you want to define a name. You add this attribute to the class informing the naming strategy to be used:
[JsonNamingStrategy(TCamelCaseNamingStrategy)]
TMySimpleClass = class
Here is the list of available naming strategies, all available from unit Bcl.Json.NamingStrategies
. The JSON examples are
based on a class with the following field and property:
FFirstName: string;
property LastName: string;
- TDefaultNamingStrategy: This is the default strategy to be used in you don't specify one. It will keep the property name as-is, field names will have leading "F" removed.
{
"FirstName": "Joe",
"LastName": "Smith"
}
- TCamelCaseNamingStrategy: Names will be camel case, with first letter in lower case. Field names will have leading "F" removed before converting.
{
"firstName": "Joe",
"lastName": "Smith"
}
- TSnakeCaseNamingStrategy: Names will be snake case: all lower case with words separated by underscores. Field names will have leading "F" removed before converting.
{
"first_name": "Joe",
"last_name": "Smith"
}
- TIdentityNamingStrategy: Property and field names will be kept-as is.
{
"FFirstName": "Joe",
"LastName": "Smith"
}
- TIdentityCamelCaseNamingStrategy: Same as TCamelCaseNamingStrategy, but no leading "F" will be removed from field name.
{
"fFirstName": "Joe",
"lastName": "Smith"
}
- TIdentitySnakeCaseNamingStrategy: Same as TSnakeCaseNamingStrategy, but no leading "F" will be removed from field name.
{
"ffirst_name": "Joe",
"last_name": "Smith"
}
Customizing JSON Serialization
In addition to including and excluding properties from the JSON serialization, you can also modify the way a field/property is serialized as JSON. XData has its default serialization behavior for the primitive types, but you can modify it using the JsonConverter attribute.
Creating the converter
The converter must inherit from TCustomJsonConverter class and override methods ReadJson and WriteJson, which will read and write the serialized JSON value. Here is a small example:
uses {...}, Bcl.Json.Converters, Bcl.Json.Reader, Bcl.Json.Writer, System.Rtti;
type
TSampleJsonConverter = class(TCustomJsonConverter)
protected
procedure ReadJson(const Reader: TJsonReader; var Value: TValue); override;
procedure WriteJson(const Writer: TJsonWriter; const Value: TValue); override;
end;
{...}
procedure TSampleJsonConverter.ReadJson(const Reader: TJsonReader; var Value: TValue);
var
S: string;
begin
S := Reader.ReadString;
if SameText(S, 'one') then
Value := 1
else
if SameText(S, 'two') then
Value := 2
else
Value := StrToInt(S);
end;
procedure TSampleJsonConverter.WriteJson(const Writer: TJsonWriter; const Value: TValue);
begin
case Value.AsOrdinal of
1: Writer.WriteString('one');
2: Writer.WriteString('two');
else
Writer.WriteString(IntToStr(Value.AsOrdinal));
end;
end;
The converter above can be applied to an integer property like this:
uses {...}, Bcl.Json.Attributes;
{...}
type
TFoo = class
private
[JsonConverter(TSampleJsonConverter)]
FProp: Integer;
With the setup above, the following behavior will apply:
- If FProp = 1, then it will be serialized as:
"FProp": "one"
- If FProp = 2, then it will be serialized as:
"FProp": "two"
- For other FProp values, it will be serialized normally as integer:
"FProp": 5
The converter also handles deserialization properly, i.e., if it reads value "one", it will set FProp as 1, and so on.
Collection of Objects
A collection of objects is represented as JSON array where each element is the representation of an entity or the representation of an entity reference, or representation of any simple object type supported by XData. An empty collection is represented as an empty JSON array.
Example of collection of entities with objects inline:
[
{
"$id": 1,
"@xdata.type": "XData.Default.Country",
"Id": 10,
"Name": "Germany",
},
{
"$id": 2,
"@xdata.type": "XData.Default.Country",
"Id": 13,
"Name": "USA",
}
]
When the server is responding to a request to an entity set resource address or a navigation property that returns an entity collection, or any service operation that returns a list of arbitrary objects, it wraps the collection in a JSON object with a name/value pair named "value":
{
"value": [
{
"$id": 1,
"@xdata.type": "XData.Default.Country",
"Id": 10,
"Name": "Germany",
},
{
"$id": 2,
"@xdata.type": "XData.Default.Country",
"Id": 13,
"Name": "USA",
}
]
}
Individual Properties
When performing requests to an URL that represents an individual property, such property is represented as a JSON object (except for blob properties).
The property is represented as an object with a single name/value pair, whose name is "value" and whose value is represented according to the XData JSON Format notation for property values.
For example, when requesting the Name property of a TCustomer object (such as from address "http://server:2001/tms/xdata/Customer(3)/Name"), result might be:
{
"value": "John Doe"
}
Note that when the URL is suffixed with "$value" segment or the property represents a blob property, then the content is the raw value of the property (or binary value), not a JSON representation.
Error Response
When an error happens in the XData server while processing a client request, the server might provide information about the error in the HTTP message body as a single JSON object, with a single name/value pair named "error" which value is also a JSON object. Such inline JSON object might contain the following name/value pairs:
"code", which provides a JSON string representing the error code for the error raised by the server;
"message", which provides a JSON string with a human-readable text describing the error.
Example:
{
"error": {
"code": "EntityNotFound",
"message": "Requested entity does not exist."
}
}
Canonical Id
The canonical id is a string representation that completely defines and identifies an Aurelius entity. It's used by XData in several places in JSON format, like when using association references.
The format of canonical id is "<entityset>(<id>)", where <entityset> is the name of the entity set which contains the entity, and <id> is the id entity in URL literal representation. The entity set must be the one which related entity type is the exact type of the entity being identifies. What this means is that if an entity belongs to more than one entity set, the type of the entity set must match the type of entity. For example, when dealing with inheritance between entity types, an entity of type "Dog" might belong to entity sets "Dog", "Mammal" and "Animal". In this case, the entity set must be "Dog".
Here are some examples of canonical id's:
"Invoice(5)"
"Customer('John')"
"InvoiceItem(15)"
It's important to note that the canonical id also represents the URI of the associated entity, relative to the server base URI. In other words, if you append the canonical id to the server base URI, you will end up with the URI of the associated entity, which you can use to retrieve, update or delete the entity (depending on the HTTP method used). For example, suppose the server base URI of the above entities is "http://myserver:2001/tms/xdata", then the resource URI of those entities are:
http://server:2001/tms/xdata/Invoice(5)
http://server:2001/tms/xdata/Customer('John')
http://server:2001/tms/xdata/InvoiceItem(15)