Crafting precise and adaptable schemas is paramount in API design. OpenAPI offers several constructs to help define flexible and robust data models, the most powerful and flexible of which are oneOf
, allOf
, and anyOf
. These keywords provide different ways to validate data against multiple schemas, making them essential for designing APIs that accommodate varied data structures while maintaining type safety.
Whether you’re designing your API using Laravel, Flask, Nest, Django, FastAPI, SpringBoot, Express, ASP.NET, or anything else. Understanding the key differences and how to use OpenAPI oneOf
, allOf
, and anyOf
will help you design a more maintainable and future-proof API.
The Key Differences
oneOf
– The data must validate against exactly one of the subschemas.allOf
– The data must validate against all the subschemas simultaneously.anyOf
– The data must validate against at least one (or more) of the subschemas.
Understanding these distinctions is crucial when designing OpenAPI definitions, as they directly impact SDK generation, data validation, and client-side type safety.
Understanding Subschemas
To understand these keywords, it’s first important to understand what a subschema is. At its simplest, a subschema is just a schema referenced in a larger, more complex schema. Take, for example, this basic schema that defines a Dog
, Cat
, and Animal
. They are all schemas, but when Dog
and Cat
are referenced in Animal,
they are subschemas of Animal
.
This is similar to classes and subclasses, but the relationships are top-down rather than bottom-up. In this example, Dog
does not inherit or extend from Animal
and does not take on any of Animal
’s attributes. Instead, Animal defines that Dog or Cat as possible subschemas that it can conform to.
components:
schemas:
Dog: # ...
Cat: # ...
Animal:
oneOf:
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Cat
OpenAPI oneOf
: Ensuring Exclusive Schema Matching
Use OpenAPI oneOf
when a value should conform to precisely one of the provided schemas. This is particularly useful when defining mutually exclusive options.
Example:
components:
schemas:
Pet:
type: object
properties:
name:
type: string
petType:
type: string
discriminator:
propertyName: petType
Dog:
allOf:
- $ref: '#/components/schemas/Pet'
- type: object
properties:
bark:
type: boolean
dig:
type: boolean
Cat:
allOf:
- $ref: '#/components/schemas/Pet'
- type: object
properties:
meow:
type: boolean
scratch:
type: boolean
Animal:
oneOf:
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Cat'
Here, an Animal
must be either a Dog
or a Cat
, but not both. This ensures that API consumers provide and receive well-structured data that conforms to differing expectations of Cat
and Dog
. For example, a user can provide a Dog
that can bark
and dig
but cannot meow
.
Pitfall: One common issue with oneOf
is its strict validation. oneOf
is exactly one. If none or more than one schema matches, validation will fail. While the “none case” is expected, the “more than one case” can confuse API consumers and developers.
OpenAPI allOf
: Merging Multiple Schemas
Use OpenAPI allOf
to create composite objects that inherit properties from multiple schemas. This is useful when reusing common schema elements while adding specific properties.
Example:
components:
schemas:
Address:
type: object
properties:
street:
type: string
city:
type: string
Person:
type: object
properties:
name:
type: string
age:
type: integer
Employee:
allOf:
- $ref: '#/components/schemas/Person'
- $ref: '#/components/schemas/Address'
- type: object
properties:
employeeId:
type: string
Here, Employee
is a combination of Person
and Address
, inheriting all their properties while adding employeeId
. This is especially useful when generating SDKs, as the resulting type automatically includes all inherited fields. Any update to the definition of Person
or Address
will be automatically reflected in Employee
.
Pitfall: Overusing allOf
can create deeply nested structures that become difficult to manage and debug, especially when resolving conflicts between inherited properties.
OpenAPI anyOf
: Flexible Validation Against Multiple Schemas
Use OpenAPI anyOf
when a value should match at least one (but possibly more) of the subschemas. This is useful for handling partial matches.
Example:
components:
schemas:
Contact:
anyOf:
- type: object
properties:
email:
type: string
format: email
- type: object
properties:
phone:
type: string
Here, a Contact
object must contain either an email
, a phone
, or both. This is useful for more flexible schemas where multiple data input formats are acceptable.
Pitfall: With anyOf
, partial matches can lead to unintended behaviors if API consumers expect all properties always to be present. Defining required fields can mitigate this.
Recommended Practices
To effectively use OpenAPI’s oneOf
, allOf
, and anyOf
, consider these best practices:
- Document Clearly: Provide detailed documentation on how your API behaves to prevent confusion for API consumers.
- Use Discriminators with
oneOf
: Implement adiscriminator
property to simplify schema resolution and avoid ambiguous validation errors. - Avoid Excessive Nesting with
allOf
: WhileallOf
promotes schema reuse, excessive inheritance can lead to deeply nested structures that are difficult to maintain. - Define Required Fields for
anyOf
: If a schema underanyOf
should always have a specific property, explicitly set it as required to avoid unexpected validation issues. - Test Thoroughly: Validate your OpenAPI specification using tools like
liblab validate
or Linters to catch misconfigurations early.
Implications for SDK Generation
Choosing between oneOf
, allOf
, and anyOf
has a significant impact on how SDKs handle type definitions:
- OpenAPI
oneOf
ensures that only one type is valid at a time, leading to strict type-checking. - OpenAPI
allOf
merges schemas, making SDKs generate fully composed types with inherited properties. - OpenAPI
anyOf
allows looser type enforcement, enabling SDKs to accept multiple valid structures.
Understanding these nuances helps design API contracts that are flexible and well-defined, ensuring a better developer experience and more reliable SDKs.
Conclusion
By effectively leveraging oneOf
, allOf
, and anyOf
, API designers can create expressive and maintainable OpenAPI schemas, enhancing the developer experience across various integrations. Whether building SDKs or validating API responses, choosing the proper construct ensures your API remains robust and easy to consume.
Before you go, check out our CLI tool that can automatically generate client libraries in 6+ languages for any API.Build an SDK For Any API