Handling complex object payloads in API Responses #4
-
Taken from API-URL Structure: "A resource SHOULD NOT dynamically change its response body schema or shape based on query parameters or other dynamic behavior. If you require a differently shaped body to be returned, consider breaking it into different resource endpoints." What if our response object includes a payload of some complex object type that happens to be JSON? The best example I can think of this is from the Rules Management API. They define a standard shape for their response models and have a dynamic shape for the property This might fall into the category of APIs that serve up complex objects, or that wrap or manage blob/clob data. Some topics for conversation are:
Examples from {
"ID": 28097,
"active": false,
"createdBy": "USER: 789436214106946475209379604081598495864968",
"createdDate": "2021-03-09T20:15:23.586606Z",
"fingerprint": "7f2fa7c5-bb34-3f58-868c-b2ee85bb0d7g",
"payload": {
"identifiersBySource": [
{
"source": "ParcelPayload",
"identifiers": [
{
"identifier": "PurchaseOrderNumber",
"pathsByLanguage": [
{
"paths": [
{
"path": "Order/Header/OrderHeader/PurchaseOrderNumber"
}
],
"queryLanguage": "xpath"
}
]
}
],
"identifiersVersion": {
"source": "Keystone",
"version": "1.0"
}
},
{
"source": "ParcelMeta",
"identifiers": [
{
"identifier": "ParcelCreatedDate",
"pathsByLanguage": [
{
"paths": [
{
"path": "$.createdDate"
}
],
"queryLanguage": "jsonPath"
}
]
}
],
"identifiersVersion": {
"source": "ParcelService",
"version": "v3"
}
}
]
},
"rule": {
"ID": 7189,
"createdBy": "USER: +564654654654",
"createdDate": "2023-01-09T20:15:23.422438Z",
"description": "Order values that identify the document",
"modifiedBy": "USER: 548598798468464654654",
"modifiedDate": "2022-09-09T20:15:23.422438Z",
"name": "Order Identifiers",
"selectors": [
{
"name": "documentType",
"value": "Order"
},
{
"name": "documentVersion",
"value": "7.7"
},
{
"name": "organization",
"value": "654987654654654654"
},
{
"name": "organizationSecondary",
"value": null
}
],
"type": {
"ID": 31
}
}
} Response model including a Document Matching rule payload: {
"ID": 27720,
"active": true,
"createdBy": "USER: 6546874984654654",
"createdDate": "2022-01-05T17:57:25.313157Z",
"fingerprint": "ae23a388-39e3-3ce9-9213-8254e5d10454",
"payload": {
"matchingSources": [
{
"sourceFilters": [
{
"value": null,
"operator": "max",
"priority": 1,
"identifier": "ParcelCreatedDate",
"compareType": "date"
}
],
"sourceDocument": "Order",
"matchingIdentifiers": [
{
"operator": "equals",
"required": false,
"compareType": "string",
"sourceIdentifier": "PurchaseOrderNumber",
"targetIdentifier": "PurchaseOrderNumber"
},
{
"operator": "equals",
"required": false,
"compareType": "string",
"sourceIdentifier": "CustomerOrderNumber",
"targetIdentifier": "CustomerOrderNumber"
},
{
"operator": "equals",
"required": false,
"compareType": "string",
"sourceIdentifier": "ReleaseNumber",
"targetIdentifier": "ReleaseNumber"
}
]
}
]
},
"rule": {
"ID": 5827,
"createdBy": "USER: 674654654654654654654654654",
"createdDate": "2022-01-05T17:57:24.918303Z",
"description": "Document matching instructions for Shipment",
"modifiedBy": "USER: 65464684654654654654",
"modifiedDate": "2019-01-05T17:57:24.918303Z",
"name": "Shipment Document Matching Rule",
"selectors": [
{
"name": "documentType",
"value": "Shipment"
},
{
"name": "documentVersion",
"value": "7.7"
},
{
"name": "organization",
"value": null
},
{
"name": "organizationSecondary",
"value": null
}
],
"type": {
"ID": 32
}
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 11 replies
-
Further ElaborationFor the example of the rule instance payload, I'm not sure how it is being stored, but the schema a rule type is well defined at For the DEX API, we have a number of endpoints that are only used for testing (at the moment) that take in both encoded RSX payloads as strings and also in-line JSON objects (which are the dynamically shaped rule instance objects). Hence, the bullet for requests vs responses. Other considerations are deserialize in-line dynamically shaped object into generic types. From my experience this can be tricky in Java/Kotlin because of variance. I am not an expert on this topic (as I am new to the Java world) but in my experience it causes a limited amount of flexibility when requesting/parsing data into a |
Beta Was this translation helpful? Give feedback.
-
I think another interesting example that can be brought up, is the Transformation Design Artifact. I know we've had various issues with this in the past. At the time there were a few different tools that all used their own defined models. IIRC new properties were added over time to help handle new features for Transformation Designer, and any tools that used those JSON designs would need to be updated. At the time I was working on a couple tools that needed to understand the complex design JSON, so I made a python package that both of my tools used. https://github.com/SPSCommerce/transformation-design-tools This required the package to be properly maintained and updated as needed, but overall I think it improved the developer experience when working with the JSON designs (in python at least) |
Beta Was this translation helpful? Give feedback.
-
This is a great concern to bring up since it's one of my favorite patterns that I see wide applicability for in many areas for us. Some existing examples have been called out and there are some others:
And I think there are some other possible places this could crop up, as well.
If we can add some thoughts around standardization for this pattern I'd be all for it. I think the use of JSON schema metamodel for JSON payloads is an "easy" example. It makes sense to me that the metamodel would be defined using other standards or DSLs, and applied against different formats of data. For example, for a Doc API we'd likely have some artifact (TD design?) that describes the validation rules for a given document (between a specified set of trading partners, doc type, and fulfillment model). Which validation artifact to use will depend on those dynamic parameters. And the structure that it's applied against would likely be RSX, but maybe eventually a new API standards compliant structure. |
Beta Was this translation helpful? Give feedback.
-
Regarding payload serialization, I do think this has got to be something we lean heavily on Content-type and Accept headers for. Encoding a payload inside a different format is cumbersome. For request data, I think the multi-part stuff can handle multiple mime-types with multiple payloads in a request. I'd imagine the same can be done for a response. Something to look at. The Ship Doc transformation API does this for passing in an xml payload and corresponding stylesheet. |
Beta Was this translation helpful? Give feedback.
Further Elaboration
For the example of the rule instance payload, I'm not sure how it is being stored, but the schema a rule type is well defined at
/rules/v2/types/{typeID}
but itself could be considered a dynamic response type. The purpose of rules/v2 as I understand it was specifically to structure the routes in this way for maintainability. As new rule types are added, they didn't want to continue to add new routes to service those rule types. Also, specifically for Rules Management, a rule type and rule instances are entities, so returning them as JSON object would be preferred. However, how we define routes, queries and response types might also have implications in other services t…