JSON Contract
This feature is part of FD Components Pro, which is documented as a preview and is not publicly available yet. See Editions and Availability.
This page specifies the canonical envelope used by FdcJsonAdapter and FdcRestAdapter. The contract is transport-independent: JSON may travel through HTTP, another service protocol, local IPC, or an application-owned client SDK.
The current contract is envelope-only. A bare array of rows is not a valid response.
Common rules
Every request and response contains a non-empty source string. A response whose source does not exactly match the configured adapter source is rejected.
Requests can include an application-defined meta object. FDC transports metadata but does not interpret it. Responses do not need to echo meta.
All JSON objects must use string keys. Values must be valid JSON values; non-finite numbers and application-specific Dart objects are rejected unless converted to transport values first.
Transport values
toMap() and toJson() convert typed values into JSON-compatible forms:
| FDC/Dart value | JSON representation |
|---|---|
null, String, bool, integer, finite number | Corresponding JSON scalar |
FdcDecimal | Exact decimal string |
DateTime | ISO 8601 string; date fields use YYYY-MM-DD in apply values |
FdcTime | Time string |
FdcGuid | GUID string |
List | Recursively converted JSON array |
Map | Recursively converted JSON object with string keys |
Using decimal strings avoids precision loss in backends whose JSON number type is binary floating point.
Open request
FdcJsonOpenEvent.toMap() creates the open envelope:
{
"source": "customers",
"meta": {
"tenant": "north-america",
"requestId": "req-1042"
},
"filters": [
{
"fieldName": "status",
"operator": "equals",
"value": "active"
}
],
"sorts": [
{
"fieldName": "company",
"sortType": "ascending"
}
],
"search": {
"text": "north",
"mode": "contains",
"caseSensitive": false,
"fields": ["company", "city"]
},
"offset": 0,
"limit": 100,
"selectedKeys": [
{"customer_id": 17}
],
"includeFields": true,
"includeTotalCount": true
}
Open request properties
| Property | Required | Meaning |
|---|---|---|
source | Yes | Logical adapter source. |
meta | No | Application-defined request metadata. |
filters | Yes | Ordered effective filters; may be empty. |
sorts | Yes | Ordered effective sorts; may be empty. |
search | No | Present only when search is active. |
offset | No | Zero-based page offset when paging is active. |
limit | No | Maximum rows requested for the page. |
selectedKeys | No | Present when selected-key-only filtering is active, including an empty array when no keys are selected. |
includeFields | No | Present as true when field metadata is requested. |
includeTotalCount | Yes | Whether the backend must return the complete matching count. |
Each filter contains fieldName, the FDC operator name, and value. caseSensitive: true is included only when requested. Sort priority follows array order.
The backend must execute the complete request before returning rows. Page slicing occurs after filtering, search, and sorting. totalCount describes the complete matching result before page slicing.
Open response
{
"source": "customers",
"rows": [
{
"customer_id": 17,
"company": "Northstar Trading",
"city": "Boston",
"credit_limit": "25000.00",
"registered_at": "2026-01-15"
}
],
"totalCount": 1248
}
Open response rules:
- the root must be a JSON object;
sourceis required, non-empty, and must match the adapter source;rowsis required and must be an array of JSON objects;totalCountis required whenincludeTotalCountistrue;totalCount, when present, must be an integer;- a bare rows array is invalid.
Response row properties are matched to dataset fields case-insensitively. The materialized row uses the field name declared in the dataset schema. A single response row must not contain duplicate keys that differ only by case, such as both Customer_ID and customer_id.
Extra response properties that do not correspond to requested dataset fields are ignored when field metadata is available. Missing fields materialize as null during a normal open.
Apply request
FdcJsonApplyEvent.toMap() emits one operation-level envelope:
{
"source": "customers",
"meta": {
"tenant": "north-america",
"requestId": "req-1043"
},
"actions": [
{
"action": "delete",
"recordId": 17,
"keys": {"customer_id": 41}
},
{
"action": "update",
"recordId": 23,
"keys": {"customer_id": 52},
"values": {"city": "Seattle"}
},
{
"action": "insert",
"recordId": 31,
"values": {
"company": "Harbor Systems",
"city": "Portland"
}
}
]
}
Actions are always ordered:
delete → update → insert
Apply action properties
| Property | Delete | Update | Insert |
|---|---|---|---|
action | delete | update | insert |
recordId | Required | Required | Required |
keys | Original key values | Original key values | Omitted |
values | Omitted | Changed values only | Insert values |
recordId is the dataset's internal correlation identifier. It is not a database primary key and must be returned unchanged when the backend supplies authoritative values for an insert or update.
Updates and deletes use original key values so a key field changed by the user still identifies the correct stored row.
Successful apply response
The minimal successful response is:
{
"source": "customers",
"success": true
}
Return optional authoritative rows when the backend generates or normalizes values:
{
"source": "customers",
"success": true,
"rows": [
{
"recordId": 31,
"values": {
"customer_id": 9104,
"company": "Harbor Systems LLC",
"updated_at": "2026-07-10T10:30:00Z"
}
}
]
}
Success response rules:
successmust be the booleantrue;errorsmust not be present;rows, when present, must be an array;- every row requires an integer
recordIdand an objectvalues; - a
recordIdmay reference an insert or update action, never a delete or unknown action; - duplicate response
recordIdvalues are rejected; - response row order is irrelevant.
totalCount is not part of an apply response.
Failed apply response
{
"source": "customers",
"success": false,
"errors": [
{
"recordId": 23,
"field": "city",
"code": "invalid_city",
"message": "The city value is not valid for this account."
}
]
}
Failure response rules:
successmust be the booleanfalse;errorsis required and must be a non-empty array;- each error requires a non-empty
message; recordIdis optional, but when present it must reference an action in the request;fieldorfieldNamecan identify the affected field;codecan carry an application-specific machine-readable value.
An error without recordId becomes an operation-level apply error. The JSON apply contract is not a mixed partial-success protocol: one envelope reports either success or failure for the complete changeset.
Aggregate request
FdcJsonAggregateEvent.toMap() creates:
{
"source": "orders",
"filters": [
{
"fieldName": "status",
"operator": "equals",
"value": "open"
}
],
"search": {
"text": "north",
"mode": "contains",
"caseSensitive": false,
"fields": ["customer"]
},
"aggregates": [
{"fieldName": "total", "aggregate": "sum"},
{"fieldName": "total", "aggregate": "avg"},
{"fieldName": "order_id", "aggregate": "count"}
]
}
Aggregate requests may also contain meta and selectedKeys. They deliberately omit sorts, offset, and limit. The backend calculates against the complete matching filter/search/selection result.
Aggregate response
Nested response form:
{
"source": "orders",
"aggregates": {
"total": {
"sum": "154280.75",
"avg": "481.22"
},
"order_id": {
"count": 321
}
}
}
The adapter also accepts flat keys:
{
"source": "orders",
"aggregates": {
"total.sum": "154280.75",
"total:avg": "481.22",
"order_id.count": 321
}
}
The root and aggregates must both be objects. Decimal sum and avg values are materialized as FdcDecimal. Missing sum values become FdcDecimal.zero; other missing aggregate values remain null.
Backend implementation checklist
Before connecting a production backend, verify that it:
- validates
sourceand returns it in every response; - executes filters, sort priority, search, selected keys, and paging exactly once;
- returns an integer
totalCountwhenever requested; - treats apply as one operation-level success or failure;
- correlates authoritative apply values by FDC
recordId; - calculates aggregates without applying page offset or limit;
- serializes decimals without losing precision;
- returns only valid JSON values and string object keys;
- produces deterministic errors for malformed requests.