Interfaces and json objects (uml class diagrams)

YASE - Yet Another Software Engineer

Interfaces and json objects (uml class diagrams)

After designing the APIs on paper, choosing endpoint names, and deciding on handler functions to manage requests, we can move on to the more concrete part: writing the interfaces. this means defining the payloads—the json objects to be passed to the APIs and returned to the client.

These interfaces usually correspond to a database entity, as we’ve seen before. in fact, APIs often serve precisely to interface with the backend database. so, it frequently happens that these interfaces are nothing more than an object with the same attributes as the entity itself. however, in addition to the fields and data present in the db, it’s good practice to add some metadata to the api interfaces. this metadata can be useful for both the end-user (who might also be a developer) and the API programmer.

So, what’s typically done is to encapsulate the returned data in a standard data structure for that request (or for the entire system). this structure contains some additional information, such as any server-side errors that generated corrupted data or no data at all, debug messages, request timestamps, and so on.

Generally, a structured language like JSON is used for these interfaces, along with best practices for its generation, including mandatory fields that facilitate internal use and client integration. Let’s look at some of the main best practices recommended by google: Google best practices for rest apis.

Key best practices

  1. property names:
    • camelcase: always use lowercamelcase format for property names (e.g., username, creationdate, listitems).
    • meaningful and ascii: choose descriptive names and use only ascii characters. avoid uncommon abbreviations.
    • english: prefer english names for greater interoperability, unless the API is strictly limited to a narrow, national context.
  2. property values & data types:
    • standard types: use standard json data types (string, number, boolean, object, array, null).
    • dates and times: represent dates, times, and timestamps as strings in iso 8601 format (e.g., "2025-03-28t08:21:58z" or "2025-03-28"). use utc (z) whenever possible to avoid timezone ambiguities.
    • durations/intervals: represent durations or amounts of time as a number (preferably in seconds or milliseconds, specifying the unit in the property name, e.g., durationseconds) or as an iso 8601 duration format string (e.g., "p3dt6h4m").
    • enumerations (enum): represent enumeration values as string. choose meaningful string values (e.g., "pending", "completed").
    • empty/absent values: use null to explicitly indicate the absence of a value. avoid using empty strings ("") for this purpose, unless an empty string is a valid value distinct from null. consider completely omitting optional properties if they don’t have a value.
    • binary data: encode binary data using base64 and include it as a string.
  3. json structure:
    • top-level object: json responses should always consist of an object ({}) or, less commonly if appropriate, an array ([]) as the root structure. it’s often useful to use a root object even for collections (e.g., { "items": [...] }) to easily add metadata (like pagination information: totalitems, nextpagetoken, etc.).
    • consistency: maintain a consistent structure and naming convention throughout the API to make it easier to use.



Which fields should you include at a minimum:

1. for general context and success:

2. for detailed error handling (typical with 4xx/5xx status codes):

3. for pagination (when returning collections/lists):



Important considerations:

In summary, including a wrapper with metadata like data, error (with code, message, details), requestid, and pagination fields makes rest APIs more robust, user-friendly, and maintainable.

Let’s put it all together:

# example of a complete response
{
  "data": {
    "userid": 123,
    "name": "john doe"
  },
  "message": "user found",
  "timestamp": "2020-07-10 15:00:00.000",
  "requestid": "rdxkm65",
  "apiversion": "v1",
  "executiontimems": 300,
  "page": 1,
  "limit": 1,
  "totalitems": 1
}

In case of an error:

{
  "timestamp": "2021-07-10 15:00:00.000",
  "requestid": "rdxkd345",
  "apiversion": "v1",
  "executiontimems": 121,
  "error": {
      "code": "validation_error",  // same as status
      "title": "insufficient funds",  // short title
      "message": "invalid input.", // long description, substitutes detail
      "details": [
        { "field": "email", "code": "invalid_format", "message": "invalid email format." },
        { "field": "age", "code": "value_too_low", "message": "age must be greater than 18." }
      ],
      "helpurl": "https://example.com/probs/insufficient-funds", // replaces "type"
      "instance": "/account/12345/transactions/67890" // points to the resource if it exists
    }
}

If the data field is present, then the http return code will be a success code, and the error container will not be present; otherwise, the error container will be present, and not the data one.

It’s advisable to include only one of the two fields, data or error, based on the http response code. if the http code indicates success (e.g., 200 ok), use the data field. if, however, the http code indicates an error (e.g., 4xx or 5xx), use the error field. this approach keeps the payload structure clear and consistent.