How to use allOf
in OpenAPI
Use of allOf
comes from the desire for reuse. When you have a single source of truth, maintenance is easier.
This makes sense. You might want to reuse a lot of things. But allOf
is not appropriate in many cases and can result in illogical schemas.
How do you know when to use allOf
and when to avoid it? This article covers:
- how to use
allOf
- how
allOf
is evaluated - valid use cases
- common language patterns that warn that
allOf
use is not appropriate
Usage of allOf
Declare allOf
as an array of schemas.
All of these keywords must be set to an array, where each item is a schema.
This works in YAML.
allOf: - title: time type: object properties: time: type: string - title: date type: object properties: date: type: string
And it works in JSON. The remainder of this article uses YAML for schema definitions.
{ "allOf": [ { "title": "time", "type": "object", "properties": { "time": { "type": "string" } } }, { "title": "date", "type": "object", "properties": { "date": { "type": "string" } } } ] }
Evaluation of allOf
A goal of JSON Schema is to be able to evaluate if JSON is valid or invalid with the defined schema.
From the definition of allOf
, it is treated like a logical AND
:
Must be valid against all of the subschemas
$time && $date
Based on our prior allOf
declaration which requires time
and date
schemas, the following JSON would match the schemas:
{ "time": "08:15:00+06:00", "date": "2022-01-22" }
Does the following JSON match the allOf
too?
{ "date": "2022-01-22" }
The "time" property is missing, and you may think that it only matches the date
schema. However, neither schema, including the time
schema, has any required properties. Therefore, it matches all of the schemas.
In the same way, the following JSON matches the allOf
schemas too.
{ "temperature": 25, "unit": "C" }
That doesn't seem right. But it is. The schema declares what some properties types must be if they are present. It didn't declare them as required.
The following schema is invalid, because date
is not a string
.
{ "temperature": 25, "unit": "C", "date": 22 }
If you declare a media type examples in your OpenAPI definition, and turn on the no-invalid-media-type-examples rule, Redocly evaluates the examples against the schema to help you evaluate them. You can also do this be evaluating real API responses with API testing. Contact us if you're interested in doing that.
Valid cases
There are times when schemas are a combination of two pre-existing schemas. If you find yourself wanting to add "with minor exceptions", then do not use the allOf
keyword, no matter how tempting.
For example, let's say you have a resource for User.
title: User required: - id - email type: object properties: id: type: string name: type: string email: type: string avatar: type: string phone: type: string dob: type: string createdAt: type: string recentLogInAt: type: string
And then you have other resources that use an excerpt of the User
schema such as id
, name
, email
, and avatar
. (The topic of API design is different from the topic of API description, and this article doesn't cover if you should design an API this way.)
In order to reuse that excerpt of the User schema, you could rework the schema as follows.
title: UserExcerpt required: - id - email type: object properties: id: type: string name: type: string email: type: string avatar: type: string
Then, you could use that in the User and any other schemas with allOf
.
title: User allOf: - $ref: '#/components/schemas/UserExcerpt' - type: object properties: phone: type: string dob: type: string createdAt: type: string recentLogInAt: type: string
And another schema could reuse it similarly.
title: ParkingSpot allOf: - $ref: '#/components/schemas/UserExcerpt' - type: object properties: licenseExpiration: type: string spotId: type: string
Siblings to $ref
s
OpenAPI 3.0 has a limitation related to reuse. Schemas have some properties that are informational and do not impact the validation of JSON. Two of those properties are summary
and description
.
A common use case is the desire to reuse the schema by change the description due to the context.
OpenAPI 3.1 allows for siblings next to the $ref
.
type: object properties: transactionId: $ref: '#/components/schemas/ResourceId' description: ID of the transaction.
OpenAPI 3.0 and prior do not allow for siblings next to the $ref
, but the allOf
keyword could be used above it as a "workaround".
type: object properties: transactionId: description: ID of the transaction. allOf: - $ref: '#/components/schemas/ResourceId'
This behavior confuses people, and some people think of it as a way to override the reference object properties. This is only for informational properties and not for properties that are used for evaluation.
Illogical schemas from allOf
misuse
Word to watch out for that could indicate misuse:
- override
- extend
Type override is invalid
Overriding a description and summary is allowed. From an evaluation perspective, it works because the description is going to match any type.
The following example references a ResourceId
schema and its type is a string
. Therefore, the following example is illogical because transactionId
cannot be an integer
and a string
.
type: object properties: transactionId: description: ID of the transaction. type: integer allOf: - $ref: '#/components/schemas/ResourceId'
Different types are invalid
The following example demonstrates illogical schemas where types mismatch within a list of schemas provided to the allOf
keyword.
allOf: - $ref: '#/components/schemas/Foo' - $ref: '#/components/schemas/Bar'
If Foo
and Bar
are not of the same type, then the logical AND
cannot be true. For example, something cannot be a string
and an object
at the same time. Sometimes, this is more difficult to notice when using reference objects. However, it's clear when written the following way.
allOf: - type: string - type: object
Closed schemas and allOf
are invalid
Even when the schemas are of the same type, there can still be illogical conflicts when using the allOf
keyword.
The following schema demonstrates and illogical conflict.
allOf: - type: object properties: date: type: string additionalProperties: false - type: object properties: time: type: string additionalProperties: false
The additionalProperties: false
means that the schema cannot have any additional properties.
The following is invalid, because it matches the first schema, but the second schema does not have date
declared as a property and it declares there cannot be any additional properties.
{ "date": "2022-01-22" }
Summary
In Swagger 2.0 or OpenAPI 3.0, use allOf
to override a description
or summary
of a schema. In OpenAPI 3.1, use a sibling to the $ref
to override a description
or summary
. Do NOT override any other properties including type
.
Use the allOf
keyword as a logical AND
. Be aware of common illogical combinations:
- mismatched types
- schemas where additional properties are not allowed
The allOf
keyword may be used with the discriminator. Also, allOf
is almost always used with at least one reference object.
Consider cases where the schema is the same with one minor exception as a possible design problem. Refactoring to use allOf
may not be a good idea for those scenarios.