OpenAPI Tips
OpenAPI Tips - Data Type & Formats
Tristan Cartledge
December 6, 2022
Announcing: OpenAPI Reference
Hi! These blog posts have been popular, so we've built an entire OpenAPI Reference Guide to answer any question you have.
It includes detailed information on data types.
Happy Spec Writing!
The Problem
The OpenAPI spec is best known for descriptions of RESTful APIs, but it's designed to be capable of describing any HTTP API whether that be REST or something more akin to RPC based calls.
That leads to the spec having a lot of flexibility baked-in: there's a lot of ways to achieve the exact same result that are equally valid in the eyes of the spec. Because of this, the OpenAPI (opens in a new tab) documentation is very ambiguous when it comes to how you should define your API.
That's why we're taking the time to eliminate some of the most common ambiguities that you'll encounter when you build your OpenAPI spec. In this case we'll be taking a look at how to effectively use data types in your OpenAPI 3.0.X spec.
Note: We will cover the differences introduced by 3.1 in a future post.
Recommended Practices
The OpenAPI Spec gives you plenty of options for describing your types, but also a lot of options to describe them loosely. Loose is fine if your goal is to have a spec that is valid, but if you are using your OpenAPI document to generate: code, documentation or other artifacts, loose will get you into trouble.
Describe your types as accurately as possible; you will not only improve the documentation of your API reducing ambiguity for end-users), but will give as much information as possible to any tools you might be using to generate code, documentation or other artifacts from your OpenAPI document. Concretely, we recommend that you:
- Describe your types as explicitly as possible by using the OpenAPI defined formats.
- Use additional validation attributes as much as possible: mark properties as required, set readOnly/writeOnly, and indicate when fields that are nullable.
Below, we will step through the different types available in OpenAPI and explain how to use formats, patterns and additional attributes to give you a spec that is descriptive and explicit.
The Data Types
In addition to an object type, for custom type definitions, the OpenAPI Specification (opens in a new tab) supports most of the “primitive” types and objects you would expect to describe what your API is capable of sending and receiving:
For each of these primitive types, there is a set of commonly-used formats (i.e. date format for string) which you can designate to enforce additional constraints on the values of a schema or field. There is also the option of associating a nullable attribute. These options lead to a number of different possibilities for describing your data.
The OpenAPI Spec also includes the ability to describe more complex relationships between types using the oneOf/anyOf/allOf attributes and providing the ability to describe enums but we will leave the discussion of them to a future blog post.
For now let's explore the various types and options available for describing your types.
string
Of the primitive types (ignoring the object type) , the string type is the most flexible type available. In addition to being able to be used to represent other types (such as “true”
, “100”
, “{\\“some\\”: \\”object\\”}”
), it supports a number of formats that overlay constraints to the type of data represented. This is useful for mapping to types in various languages if you are using the OpenAPI spec for code generation.
Formats
The string type via the OpenAPI Specification officially supports the below formats:
Type | Format | Explanation | Example |
---|---|---|---|
string | date | An RFC3339 (opens in a new tab) formatted date string | “2022-01-30” |
string | date-time | An RFC3339 (opens in a new tab) formatted date-time string | “2019-10-12T07:20:50.52Z” |
string | password | Provides a hint that the string may contain sensitive information. | “mySecretWord1234” |
string | byte | Base-64 encoded data. | “U3BlYWtlYXN5IG1ha2VzIHdvcmtpbmcgd2l0aCBBUElzIGZ1biE=” |
string | binary | Binary data, used to represent the contents of a file. | “01010101110001” |
The format attribute can also be used to describe a number of other formats the string might represent but outside the official list above, those formats might not be supported by tooling that works with the OpenAPI Spec, meaning that they would be provided more as hints to end-users of the API:
- uuid
- uri
- hostname
- ipv4 & ipv6
- and others
Below are some examples of describing various string types:
# A basic stringschema: type: string# A string that represents a RFC3339 formatted date-time stringschema: type: string format: date-time# A string that represents a enum with the specified valuesschema: type: string enum: - "one" - "two" - "three"# A string that represents a fileschema: type: string format: binary
Patterns
The string type also has an associated pattern attribute that can be provided to define a regular expression that should be matched by any string represented by that type. The format of the regular expression is based on Javascript (opens in a new tab) and therefore could describe regular expressions that might not be supported by various tools or target languages, so make sure to check the compatibility with your intended targets.
Example of a string defined with a regex pattern:
# A string that must match the specified patternschema: type: string pattern: ^[a-zA-Z0-9_]*$
number/integer
The number/integer types allows the describing of various number formats through a combination of the type and format attribute, along with a number of attributes for validating the data, the spec should cover most use cases.
Available formats are:
Type | Format | Explanation | Example |
---|---|---|---|
number | Any number integer/float at any precision. | 10 or 1.9 or 9223372036854775807 | |
number | float | 32-bit floating point number. | 1.9 |
number | double | 64-bit floating point number. | 1.7976931348623157 |
integer | Any integer number. | 2147483647 or 9223372036854775807 | |
integer | int32 | 32-bit integer. | 2147483647 |
integer | int64 | 64-bit integer. | 9223372036854775807 |
Below are some examples of defining number/integer types:
# Any numberschema: type: number# A 32-bit floating point numberschema: type: number format: float# A 64-bit floating point numberschema: type: number format: double# Any integerschema: type: integer# A 32-bit integerschema: type: integer format: int32# A 64-bit integerschema: type: integer format: int64
Various tools may treat a number/integer without a format attribute as a type capable of holding the closest representation of that number in the target language. For example, a number might be represented by a double, and an integer by an int64. Therefore, it's recommended that you be explicit with the format of your number type and always populate the format attribute.
The number type also has some optional attributes for additional validation:
- minimum: The minimum inclusive number the value should contain.
- maximum: The maximum inclusive number the value should contain.
- exclusiveMinimum: Make the minimum number exclusive.
- exclusiveMaximum: Make the maximum number exclusive.
- multipleOf: Specify the number/integer is a multiple of the provided value.
Some examples are below:
# An integer with a minimum inclusive value of 0schema: type: integer format: int32 minimum: 10# An integer with a minimum exclusive value of 0schema: type: integer format: int32 minimum: 0 exclusiveMinimum: true# A float with a range between 0 and 1schema: type: number format: float minimum: 0 maximum: 1# A double with an exclusive maximum of 100schema: type: number format: double maximum: 100 exclusiveMaximum: true# An 64 but integer that must be a multiple of 5schema: type: integer format: int64 multipleOf: 5
boolean
The boolean type is simple; it represents either true or false. Be aware that it doesn't support other truthy/falsy values like: 1 or 0, an empty string “” or null. It has no additional attributes to control its format or validation.
# A boolean typeschema: type: boolean
array
The array type provides a way of defining a list of other types through providing an items attribute that represents the schema of the type contained in the array.
# An array of stringschema: type: array items: type: string# An array of objectsschema: type: array items: type: object properties: name: type: string age: type: integer# An array of arbitrary thingsschema: type: array items: {}
The array type will support any schema that describes any other type in its items attribute including types using oneOf/anyOf/allOf attributes. The array type also has some optional attributes for additional validation:
- minItems: The minimum number of items the array must contain.
- maxItems: The maximum number of items the array must contain.
- uniqueItems: The array must contain only unique items.
# An array of floats that must contain at least 1 element.schema: type: array items: type: number format: float minItems: 1# An array of strings that must contain at most 10 elements.schema: type: array items: type: string maxItems: 10# An array of booleans that must contain exactly 3 elements.schema: type: array items: type: boolean minItems: 3 maxItems: 3# An array of strings that must contain only unique elements.schema: type: array items: type: string uniqueItems: true
object
The object type allows simple and complex objects, dictionaries and free form objects, along with a number of attributes to control validation.
Fully typed object
Fully typed objects can be described by providing a properties attribute that lists each property of the object and its associated type.
# A fully typed objectschema: type: object properties: name: type: string age: type: integer format: int32 active: type: boolean# A fully typed object with a nested objectschema: type: object properties: name: type: string age: type: integer format: int32 active: type: boolean address: type: object properties: street: type: string city: type: string state: type: string zip: type: string
Objects with properties have access to some additional attributes that allow the objects to be validated in various ways:
- required: A list of properties that are required. Specified at the object level.
- readOnly: A property that is only available in a response.
- writeOnly: A property that is only available in a request.
# A fully typed object with all fields requiredschema: type: object properties: name: type: string age: type: integer format: int32 active: type: boolean required: - name - age - active# A fully typed object with only one field requiredschema: type: object properties: name: type: string age: type: integer format: int32 active: type: boolean required: - name# A fully typed object with some field as read onlyschema: type: object properties: name: type: string age: type: integer format: int32 active: type: boolean readOnly: true # This field is only returned in a response required: - name - age - active # This field will only be required in a response# A fully typed object with some field as write onlyschema: type: object properties: name: type: string age: type: integer format: int32 active: type: boolean isHuman: type: boolean writeOnly: true # This field is only required in a request required: - name - age - active - isHuman # This field will only be required in a request
Using Object for Dictionaries
The object type can also be used to describe dictionaries/maps/etc that use strings for keys and support any value type that can be described by the OpenAPI Spec.
# A dictionary of string valuesschema: type: object additionalProperties: type: string# A dictionary of objectsschema: type: object additionalProperties: type: object properties: name: type: string age: type: integer format: int32
You can also describe dictionaries that will contain certain keys
# A dictionary that must contain at least the specified keysschema: type: object properties: name: type: string # Must match type of additionalProperties required: - name additionalProperties: type: string
When using the additionalProperties attribute you can also specify additional attributes to validate the number of properties in the object:
- minProperties: The minimum number of properties allowed in the object.
- maxProperties: The maximum number of properties allowed in the object.
For example:
# A dictionary of string values that has at least one key.schema: type: object additionalProperties: type: string minProperties: 1# A dictionary of string values that has at most 10 keys.schema: type: object additionalProperties: type: string maxProperties: 10# A dictionary of string values that has 1 key.schema: type: object additionalProperties: type: string minProperties: 1 maxProperties: 1
Free form objects
The object type can also be used to describe any arbitrary key/value pair (where the keys are still required to be strings).
# An arbitrary object/dictionary that can contain any value.schema: type: object additionalProperties: true# An alternate way to specify an arbitrary object/dictionary that can contain any value.schema: type: object additionalProperties: {}
null
OpenAPI 3.0.X doesn't support a null type but instead allows you to mark a schema as being nullable. This allows that type to either contain a valid value or null.
# A nullable stringschema: type: string nullable: true# A nullable integerschema: type: integer format: int32 nullable: true# A nullable booleanschema: type: boolean nullable: true# A nullable arrayschema: type: array items: type: string nullable: true# A nullable objectschema: type: object properties: foo: type: string nullable: true