Generic fields
Most fields in any API, whether in a request, a resource, or a custom response, have a specific type or schema. This schema is part of the contract that developers write their code against.
However, occasionally it is appropriate to have a generic or polymorphic field of some kind that can conform to multiple schemata, or even be entirely free-form.
Guidance
Section titled “Guidance”While generic fields are generally rare, a API may introduce generic field where necessary. There are several approaches to this depending on how generic the field needs to be; in general, APIs should attempt to introduce the “least generic” approach that is able to satisfy the use case.
For example, an API should not use a completely generic field (such as
google.protobuf.Struct in protobuf APIs) when the value of the field must
correspond to one of a known number of schemas. Instead, the API should use
a oneof to represent the known schemas.
A oneof (proto) or oneOf (OpenAPI) may be used to introduce a type
union: the user or API is able to specify one of the fields or schemas.
Additionally, it may be used with the same type (usually strings) to
represent a semantic difference between the options.
A oneof preserves the largest degree of type safety and semantic meaning for
each option, and APIs should generally prefer them over other generic or
polymorphic options when feasible. However, the oneof construct is ill-suited
when there is a large (or unlimited) number of potential options, or when there
is a large resource structure that would require a long series of “cascading
oneofs”.
Because the individual fields in the oneof have different keys, a developer
can programmatically determine which (if any) of the fields is populated.
Moving existing fields into or out of a oneof creates a
backwards-incompatible change in Go protobuf stubs.
components: schemas: PaymentMethod: type: object properties: payment: oneOf: - type: object properties: creditCard: type: string description: Credit card token - type: object properties: bankAccount: type: string description: Bank account identifier - type: object properties: giftCard: type: string description: Gift card codeBecause the individual schemas in the oneOf can be distinguished (through
discriminators or schema validation), a developer can programmatically
determine which (if any) of the schemas matches the provided value.
Maps may be used in situations where many values of the same type are needed, but the keys are unknown or user-determined.
Maps are usually not appropriate for generic fields because the map values all share a type, but occasionally they are useful. In particular, a map can sometimes be suited to a situation where many objects of the same type are needed, with different behavior based on the names of their keys (for example, using keys as environment names).
components: schemas: Configuration: type: object properties: environments: type: object additionalProperties: type: object properties: replicas: type: integer region: type: stringMaps are represented using additionalProperties in OpenAPI.
Free-form objects
Section titled “Free-form objects”Free-form objects may be used to represent arbitrary nested JSON. Keys can be strings, and values can be numbers, strings, booleans, arrays, or additional nested objects, allowing for an arbitrarily nested structure that is represented as JSON.
A free-form object is most useful when the API does not know the schema in advance, or when a API needs to store and retrieve arbitrary but structured user data. Using a free-form object is convenient for users in this case because they can easily get JSON objects that can be natively manipulated in their environment of choice.
If a API needs to reason about the schema of a free-form object, it should use JSONSchema for this purpose. Because JSONSchema is itself JSON, a valid JSONSchema document can itself be stored in a free-form object.
The google.protobuf.Struct object represents arbitrary nested JSON,
and is automatically represented as JSON when using REST/JSON.
components: schemas: CustomResource: type: object properties: name: type: string metadata: type: object description: Arbitrary structured data additionalProperties: trueFree-form objects are represented using type: object with
additionalProperties: true or without defined properties.
The google.protobuf.Any object can be used to send an arbitrary
serialized protocol buffer and a type definition.
However, this introduces complexity, because an Any becomes useless for any
task other than blind data propagation if the consumer does not have access to
the proto. Additionally, even if the consumer does have the proto, the
consumer has to ensure the type is registered and then deserialize manually,
which is an often-unfamiliar process.
components: schemas: GenericValue: type: object properties: data: {} # Empty schema - accepts any valueAn empty schema (represented as {}) accepts any valid JSON value, including
primitives, objects, and arrays. This is the OpenAPI equivalent of
google.protobuf.Any.