Why You Need “oneOf” in Your API Specifications

Working with APIs, I come across a lot of interesting feature requests. Among those, oneOf was something that a lot of the developers want, but scarce information is found on the topic. This blog will take you through my journey of how I acquainted myself with the role of  “oneOf” in OpenAPI specifications.

What is oneOf?

According to OpenAPI Specification:

“oneOf is simply a keyword defined by OpenAPI 3.0 which can be used to combine schemas.”

To give more context, schemas are patterns of how data can be sent and received using an API. So, oneOf can make a developer’s life easier when they need to define a schema that can be validated against multiple criteria.

The keyword oneOf, as the name suggests, is bound by the rule that the incoming value in a request or a response should actually be valid against only one of the schemas defined by the API specification.

Here’s an Example..

To understand how oneOf can help your API, have a look at this OpenAPI specification

One of the paths/endpoints defined in this API requires you to send data of a pet either a cat or a dog. This means that the incoming data will be validated against the schemas defined for a cat or a dog, but not both. It should satisfy exactly “one of” the defined schema. 

And it makes perfect sense because your pet cannot be both a cat and a dog. In a cartoon, sure, but at least not in the real world. Newsflash, CatDog can never be.

Where to Use oneOf in OpenAPI Specifications?

Here are some of the ways you can use oneOf in your everyday API development. 

Custom vs Primitive Types

There are a lot of dimensions where oneOf is applied; one would be whether you’re using it to define primitive or custom types. This means that the data you want the API to process can either be schemas (i.e. custom types) or simply a number, boolean, or a string. 

This example depicts how oneOf is used when you want data to be validated against multiple schemas of primitive type.

"paths": {
    ...
    "schema": {
    "oneOf": [
        {
        "type": "integer",
        "format": "int32"
        },
        {
        "type": "string"
        }
    ]
    }
},
"required": true

You can describe your API to validate your data against custom types as well. Take a look at this example:

"paths": {
    ...
    "oneOf": [
        {
        "$ref": "#/components/schemas/Evening"
        },
        {
        "$ref": "#/components/schemas/Morning"
        }
    ],
    "description": "Course session",
    "example": {
        "startsAt": "startsAtDummy",
        "endsAt": "endsAtDummy",
        "offerDinner": true
    }
    },
    "example": {
    "startsAt": "startsAtDummy",
    "endsAt": "endsAtDummy",
    "offerDinner": true
    },
"required": true

Field vs Parameter

Jumping into more detail, you can also define oneOf at either the parameter level or the field level. This means that a value expected by an endpoint can be constrained using oneOf, where it is directly sent via the API parameters. 

In the other case, oneOf is used within the schema to make sure that the field of the schema can accept and validate against different types of schemas or simple types. 

For example, in the following JSON, the property “session” of schema “Time” is defined as a oneOf between two custom types. Hence, “session” can be validated against exactly one of the schemas: “Morning” or “Evening”.

"components": {
    "schemas": {
      "Time": {
        "title": "Time",
        "type": "object",
        "properties": {
          "session": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Morning"
              },
              {
                "$ref": "#/components/schemas/Evening"
              },
              {
                "nullable": true
              }
            ],
            "description": "Course session"
          }
        },
        "description": "This class contains a simple case of oneOf."
      },

The customer requests I’ve seen up till now are a good blend of oneOf at the endpoint level and at the field level. Also, note that all different parameter types (e.g. form, body, etc.) can take advantage of oneOf constraints.

Maps, Arrays, and Stuff

Things get a little more complicated when oneOf is combined with the form of data that the API receives or sends. For example, if you want to send an array with a oneOf type, then the array can have items corresponding to each schema defined, but the items in the array have to be validated against precisely one schema. 

More complicated cases include maps, arrays expected at the field and parameter level, and many more. A quick way to understand which data validates against which schema in oneOf is to use JSON schema validator. This validator will automatically tell you what is valid and what isn’t. 

You can construct many valid cases using different data structures, but you will need to have clarity on the goal that you’re trying to achieve through a complex structure. Otherwise one can easily pollute their API specification file by using incorrectly implemented use cases with oneOf.

Request vs Response

It is possible to take advantage of this keyword when sending a request and also while expecting a response that can be matched with exactly one listed type. Using oneOf in response types can help your API describe a variety of schemas, and consequently, the server response will validate against one of those described schemas.

XML vs JSON

API can expect and receive XML or JSON data types. JSON is much more in demand as compared to XML but I’ve still seen developers wanting support for oneOf in XML as well. At SDK level, the implementation for XML is pretty different in untyped languages like Python as compared to JSON but if we consider the code samples, they aren’t that different.

Supporting oneOf Use Cases in APIMatic

APIMatic is a developer experience platform that focuses on boosting API adoption through multi-language SDKs and API documentation. The SDKs are built automatically for your API and support oneOf in the aforementioned use cases by translating your OpenAPI specification file into the SDKs.

At APIMatic, our Java SDK has announced its beta support for oneOf, and many other languages are in progress to support this feature. Learn more about how APIMatic takes your OpenAPI specification as input and creates production ready SDKs and API documentation for your API. Happy coding!