OpenAPI Tips - Data Type & Formats
Tags: openapi, api, tutorial
Published: 2022-12-19 11:07:06

## 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 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 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 for generation**. 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 whenever fields 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 both descriptive and explicit.

## The Data Types 
In addition to an `object` type, for custom type definitions, the [OpenAPI Specification](https://spec.openapis.org/oas/latest.html#data-types) supports most of the “primitive” types and objects you would expect:
- [string](https://swagger.io/docs/specification/data-models/data-types/#string)
- [number](https://swagger.io/docs/specification/data-models/data-types/#numbers)
- [integer](https://swagger.io/docs/specification/data-models/data-types/#numbers)
- [boolean](https://swagger.io/docs/specification/data-models/data-types/#boolean)
- [array](https://swagger.io/docs/specification/data-models/data-types/#array)

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 format options available.

## string

Of the primitive types (ignoring the `object` type) , the `string` type is the most flexible type available, and the one you will use most. In addition to being able to represent other types (such as “true”, “100”, “{\“some\”: \”object\”}”), it supports a number of formats that overlay constraints on the data represented. If you are using the OpenAPI spec for code generation, this is important for correctly mapping to types in various languages.

### Formats

The string type via the OpenAPI Specification officially supports the below formats:

| type  | format | explanation  | example  
|------------|----------|-----------|-----------|
| string     | date     | An RFC3339 formatted date |“2022-01-30” |
| string     | date-time| An RFC3339 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    | Any integer number    | “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: 
- email
- uuid
- uri
- hostname
- ipv4 & ipv6
- and others

Below are some examples of describing various string types:

```yaml
# A basic string
schema:
    type: string

# A string that represents a RFC3339 formatted date-time string
schema:
    type: string
    format: date-time

# A string that represents a enum with the specified values
schema:
    type: string
    enum:
      - "one"
      - "two"
      - "three"

# A string that represents a file
schema:
    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](https://262.ecma-international.org/5.1/#sec-15.10.1)** 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:

```yaml
# A string that must match the specified pattern
schema:
    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:

```yaml
# Any number
schema:
    type: number

# A 32-bit floating point number
schema:
    type: number
    format: float

# A 64-bit floating point number
schema:
    type: number
    format: double

# Any integer
schema:
    type: integer

# A 32-bit integer
schema:
    type: integer
    format: int32

# A 64-bit integer
schema:
    type: integer
    format: int64
```

Various tools that consume an OpenAPI spec 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:

```yaml
# An integer with a minimum inclusive value of 0
schema:
    type: integer
    format: int32
    minimum: 10

# An integer with a minimum exclusive value of 0
schema:
    type: integer
    format: int32
    minimum: 0
    exclusiveMinimum: true

# A float with a range between 0 and 1
schema:
    type: number
    format: float
    minimum: 0
    maximum: 1

# A double with an exclusive maximum of 100
schema:
    type: number
    format: double
    maximum: 100
    exclusiveMaximum: true

# An 64 but integer that must be a multiple of 5
schema:
    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.

```yaml
# A boolean type
schema:
    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.

```yaml
# An array of string
schema:
    type: array
    items:
        type: string

# An array of objects
schema:
    type: array
    items:
        type: object
        properties:
            name:
                type: string
            age:
                type: integer

# An array of arbitrary things
schema:
    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.

```yaml
# 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.

```yaml
# A fully typed object
schema:
    type: object
    properties:
        name:
            type: string
        age:
            type: integer
            format: int32
        active:
            type: boolean

# A fully typed object with a nested object
schema:
    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.

```yaml
# A fully typed object with all fields required
schema:
    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 required
schema:
    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 only
schema:
    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 only
schema:
    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.

```yaml
# A dictionary of string values
schema:
    type: object
    additionalProperties:
        type: string

# A dictionary of objects
schema:
    type: object
    additionalProperties:
        type: object
        properties:
            name:
                type: string
            age:
                type: integer
                format: int32
```

You can also describe dictionaries that will contain certain keys:

```yaml
# A dictionary that must contain at least the specified keys 
schema:
    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:

```yaml
# 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).

```yaml
# 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.

```yaml
# A nullable string
schema:
    type: string
    nullable: true

# A nullable integer
schema:
    type: integer
    format: int32
    nullable: true

# A nullable boolean
schema:
    type: boolean
    nullable: true

# A nullable array
schema:
    type: array
    items:
        type: string
    nullable: true

# A nullable object
schema:
    type: object
    properties:
        foo:
            type: string
    nullable: true
```