← Learning

JSON Guidelines

Purpose

A set of guidelines for the design of JSON schemas in our APIs. They are suggestions, not hard and fast rules, and may be violated as necessary (but should be well-documented with explanation).

Values

  • Be strict and explicit in both what you accept and what you produce.
  • JSON is merely a grammar of unicode strings, we impose order and limitations on valid expressions in this grammar to avoid unnecessary complexity for us and for users of the API.

Glossary

  • see ECMA-404 for definitions of the following terms: value, object, array, string, number, true, false, null, and name.
  • boolean - A value that is either true or false.
  • integer - A number without a decimal point (.).
  • schema - A description, whether formal or informal, of the allowed structure and constraints of a particular JSON payload.
  • key - Another term for the name in a name/value pair.
  • field - Another term for the value in a name/value pair.

Structure

  1. The top-level element in a JSON payload is always an object.
  2. All the possible keys of an object are finite and defined clearly in the schema. In other words, data should not be encoded in keys.

Optional Values

There are two approaches to encoding optional values (values which may be occupied or empty) in objects. An API should pick one of the following and be consistent throughout.

1. Empty as Null

In this style, optional values should be encoded as null when empty. This means that the API should expect and accept optional fields with the null value on ingress, and serialize optional fields as null when empty on egress.

2. Empty as Missing

In this style, optional values should not appear in an object when empty. This means that the API should expect that the names of optional fields may not be included in an object, signalling that those values are empty, and should not accept null values. On egress, the names of optional fields that are empty will not appear in the objects as serialized.

Naming

  1. Object keys are camelCase, not snake_case, hyphen-case, or anything else.

Case-Sensitive

  1. Do not allow for case-insensitive parsing of keys.
  2. If the schema requires specific string values, those values should be lower-cased unless referring to something in an external standard or document or are proper nouns. Parsing of those values must also be case-sensitive.

Limits

  1. There should be no opportunity for unbounded payloads.
  2. If only integral values make sense, specify the type of a field as an integer (not a number) and be strict about accepting only integers.
  3. Provide minimum and maximum values for numbers and integers. The API should not accept or produce anything outside that range.
  4. Provide maximum length for string values and optionally a minimum (defaults to 0, allowing for the empty string “”). Measure the length of the decoded (unicode) string for validation, not the UTF8 byte encoding.
  5. Provide a maximum number of elements in a provided array.

Types / Schema

  1. Types of fields should not be mixed. A boolean field must either be true, false, or the field may be absent entirely (only if the field is explicitly optional).

  2. Each field in a payload should be a single type from the following list. For example, fields that accept arrays should not also accept single, non-array values.

  3. A recursive list of possible types (further constraints may be added):

    1. boolean: must be one of true or false.
    2. number: must be an unquoted number, must have min/max values. May be further limited to integers only.
    3. string: must have length limit. May be limited to a restricted set of values (like an enum) or have other constraints (must be well-formed URL, Timestamp, etc).
    4. array: must have maximum size, may also have minimum size (defaults to 0, the empty array []). Arrays of single values must not be “unboxed”. The type of element in array must be a single type from this list.
    5. object: must have a explicit, finite list of possible keys. Keys are specified as either required or optional (see Optional Values section). Each key is associated with a single type from this list for the value (no sum types). This is the type of the root element in a JSON payload.

Other

  1. JSON is defined as a grammar of strings of unicode code points. The de-facto standard byte-encoding of these unicode strings is UTF-8. All our APIs should accept and produce UTF-8 encoded JSON payloads.
  2. We never expect, accept, or produce invalid JSON payloads. So, comments are not allowed, object keys must be double-quoted, etc. see ECMA-404 for details.
  3. Avoid excessive nesting of values. Nesting is appropriate and necessary, but avoid adding extra layers of nesting for no purpose.
  4. Avoid semantic positioning of values inside arrays. Instead of encoding first and last name as an array of two fields [ “Jimmy”, “Dean” ], instead encode those values in an object: { “firstName” : “Jimmy”, “lastName” : “Dean” }. The latter structure is much more flexible, easy to understand, extensible, etc.
  5. All constraints are validated in first implementation. It’s easier to relax constraints in an API that is already in use than it is to add them. It is considered a bug if an API does not reject invalid payloads with an HTTP 400 response