|
| 1 | +--- |
| 2 | +title: "OpenAPI: AllOf schemas" |
| 3 | +description: "Configure how `allOf` constructs are merged with deep or shallow merge strategies." |
| 4 | +slug: "/customize-sdks/allof-schemas/" |
| 5 | +--- |
| 6 | + |
| 7 | +import { Callout } from "@/mdx/components"; |
| 8 | + |
| 9 | +# The `allOf` keyword |
| 10 | + |
| 11 | +The OpenAPI `allOf` keyword enables schema composition by merging multiple schema definitions into a single schema. Speakeasy provides two strategies for handling `allOf` merging: shallow merge and deep merge. |
| 12 | + |
| 13 | +## Merge strategies |
| 14 | + |
| 15 | +The `schemas.allOfMergeStrategy` configuration option in `gen.yaml` controls how Speakeasy merges `allOf` schemas. This setting is located under the `generation` section of the configuration file. |
| 16 | + |
| 17 | +```yaml |
| 18 | +generation: |
| 19 | + schemas: |
| 20 | + allOfMergeStrategy: deepMerge # or shallowMerge |
| 21 | +``` |
| 22 | +
|
| 23 | +### Available strategies |
| 24 | +
|
| 25 | +**`deepMerge` (default for new SDKs)**: Recursively merges nested properties within objects, preserving properties from all schemas in the `allOf` array. |
| 26 | + |
| 27 | +**`shallowMerge` (legacy behavior)**: Replaces entire property blocks when merging, which can result in lost properties from earlier schemas. |
| 28 | + |
| 29 | +<Callout title="Default behavior" type="info"> |
| 30 | +New SDKs default to `deepMerge`. Existing SDKs continue to use `shallowMerge` unless explicitly changed. Switching from `shallowMerge` to `deepMerge` may be a breaking change for existing SDKs. |
| 31 | +</Callout> |
| 32 | + |
| 33 | +## Deep merge behavior |
| 34 | + |
| 35 | +With `deepMerge` enabled, nested properties are recursively combined rather than replaced. This is particularly useful when extending base schemas with additional nested properties. |
| 36 | + |
| 37 | +Consider this OpenAPI definition: |
| 38 | + |
| 39 | +```yaml |
| 40 | +components: |
| 41 | + schemas: |
| 42 | + Base: |
| 43 | + type: object |
| 44 | + properties: |
| 45 | + id: |
| 46 | + type: string |
| 47 | + metadata: |
| 48 | + type: object |
| 49 | + properties: |
| 50 | + createdAt: |
| 51 | + type: string |
| 52 | + updatedAt: |
| 53 | + type: string |
| 54 | + Extended: |
| 55 | + allOf: |
| 56 | + - $ref: '#/components/schemas/Base' |
| 57 | + - type: object |
| 58 | + properties: |
| 59 | + name: |
| 60 | + type: string |
| 61 | + metadata: |
| 62 | + type: object |
| 63 | + properties: |
| 64 | + deletedAt: |
| 65 | + type: string |
| 66 | +``` |
| 67 | + |
| 68 | +With `deepMerge`, the resulting merged schema preserves all metadata properties: |
| 69 | + |
| 70 | +```yaml |
| 71 | +components: |
| 72 | + schemas: |
| 73 | + Extended: |
| 74 | + type: object |
| 75 | + properties: |
| 76 | + id: |
| 77 | + type: string |
| 78 | + metadata: |
| 79 | + type: object |
| 80 | + properties: |
| 81 | + createdAt: |
| 82 | + type: string |
| 83 | + updatedAt: |
| 84 | + type: string |
| 85 | + deletedAt: |
| 86 | + type: string |
| 87 | + name: |
| 88 | + type: string |
| 89 | +``` |
| 90 | + |
| 91 | +The `metadata` object includes all three timestamp properties: `createdAt`, `updatedAt`, and `deletedAt`. |
| 92 | + |
| 93 | +## Shallow merge behavior |
| 94 | + |
| 95 | +With `shallowMerge`, entire property blocks are replaced during merging. When the same property name appears in multiple schemas, only the last occurrence is retained. |
| 96 | + |
| 97 | +Using the same example from above with `shallowMerge`: |
| 98 | + |
| 99 | +```yaml |
| 100 | +components: |
| 101 | + schemas: |
| 102 | + Extended: |
| 103 | + type: object |
| 104 | + properties: |
| 105 | + id: |
| 106 | + type: string |
| 107 | + metadata: |
| 108 | + type: object |
| 109 | + properties: |
| 110 | + deletedAt: |
| 111 | + type: string |
| 112 | + name: |
| 113 | + type: string |
| 114 | +``` |
| 115 | + |
| 116 | +The `metadata` properties `createdAt` and `updatedAt` from the `Base` schema are lost because the entire `metadata` properties block was replaced by the one in the `Extended` schema. |
| 117 | + |
| 118 | +## Configuration |
| 119 | + |
| 120 | +To configure the merge strategy, add the `schemas.allOfMergeStrategy` option to the `generation` section of the `gen.yaml` file: |
| 121 | + |
| 122 | +```yaml |
| 123 | +generation: |
| 124 | + schemas: |
| 125 | + allOfMergeStrategy: deepMerge |
| 126 | +``` |
| 127 | + |
| 128 | +### Switching strategies |
| 129 | + |
| 130 | +Changing from `shallowMerge` to `deepMerge` may affect the generated SDK's type definitions and could be a breaking change. Review the impact on existing generated code before making this change in production SDKs. |
| 131 | + |
| 132 | +To explicitly use the legacy behavior: |
| 133 | + |
| 134 | +```yaml |
| 135 | +generation: |
| 136 | + schemas: |
| 137 | + allOfMergeStrategy: shallowMerge |
| 138 | +``` |
| 139 | + |
| 140 | +## Use cases |
| 141 | + |
| 142 | +### Deep merge use cases |
| 143 | + |
| 144 | +Deep merge is beneficial when: |
| 145 | + |
| 146 | +- Extending base schemas with additional nested properties |
| 147 | +- Combining request and response schemas with shared metadata objects |
| 148 | +- Working with APIs that use `allOf` to compose complex nested structures |
| 149 | +- Maintaining all properties across schema inheritance hierarchies |
| 150 | + |
| 151 | +### Shallow merge use cases |
| 152 | + |
| 153 | +Shallow merge may be appropriate when: |
| 154 | + |
| 155 | +- Maintaining backward compatibility with existing SDKs |
| 156 | +- Intentionally replacing entire nested objects from base schemas |
| 157 | +- Working with simple schema compositions without nested property conflicts |
| 158 | + |
| 159 | +## Advanced example |
| 160 | + |
| 161 | +This pattern is common in APIs that separate shared components from operation-specific requirements and examples: |
| 162 | + |
| 163 | +```yaml |
| 164 | +paths: |
| 165 | + /clusters: |
| 166 | + post: |
| 167 | + operationId: createCluster |
| 168 | + requestBody: |
| 169 | + content: |
| 170 | + application/json: |
| 171 | + schema: |
| 172 | + allOf: |
| 173 | + - $ref: '#/components/schemas/Cluster' |
| 174 | + - type: object |
| 175 | + required: |
| 176 | + - spec |
| 177 | + properties: |
| 178 | + spec: |
| 179 | + type: object |
| 180 | + required: |
| 181 | + - displayName |
| 182 | + - availability |
| 183 | + - type: object |
| 184 | + properties: |
| 185 | + spec: |
| 186 | + type: object |
| 187 | + properties: |
| 188 | + environment: |
| 189 | + example: { id: 'env-00000' } |
| 190 | + network: |
| 191 | + example: { id: 'n-00000' } |
| 192 | +``` |
| 193 | + |
| 194 | +With `deepMerge`, the final `spec` object combines the required fields from the second schema with the examples from the third schema, while preserving all properties from the referenced `Cluster` component. |
| 195 | + |
| 196 | +With `shallowMerge`, the `spec` properties from the second schema would be lost entirely, replaced by only the example properties from the third schema. |
0 commit comments