# How to use `oneOf` and `anyOf` in OpenAPI Use of `oneOf` and `anyOf` comes from the need to describe data that can take multiple different forms. When you need to validate against alternative schemas, **`oneOf` should be your preferred approach**. This makes sense. You might want to handle different payload structures, alternative response formats, or polymorphic data. `oneOf` provides clear, predictable validation where data must conform to exactly one well-defined schema. `anyOf`, while available, creates parsing ambiguity and unpredictable outcomes when multiple schemas match. **Before reaching for `anyOf`, consider refining your schema design to use `oneOf` with proper discriminators.** How do you know when to use `oneOf` and when schema redesign is needed? This article covers: - how to use `oneOf` and `anyOf` - why `oneOf` provides better predictability - valid use cases and schema design patterns - when `anyOf` might be unavoidable (and its trade-offs) ## Usage of `oneOf` and `anyOf` Both `oneOf` and `anyOf` are declared as arrays of schemas, similar to `allOf`. > All of these keywords must be set to an array, where each item is a schema. ### `oneOf` example This works in YAML. ```yaml oneOf: - title: CreditCard type: object properties: type: type: string enum: [credit] cardNumber: type: string cvv: type: string required: - type - cardNumber - cvv - title: BankAccount type: object properties: type: type: string enum: [bank] accountNumber: type: string routingNumber: type: string required: - type - accountNumber - routingNumber ``` And it works in JSON. The remainder of this article uses YAML for schema definitions. ```json { "oneOf": [ { "title": "CreditCard", "type": "object", "properties": { "type": { "type": "string", "enum": ["credit"] }, "cardNumber": { "type": "string" }, "cvv": { "type": "string" } }, "required": ["type", "cardNumber", "cvv"] }, { "title": "BankAccount", "type": "object", "properties": { "type": { "type": "string", "enum": ["bank"] }, "accountNumber": { "type": "string" }, "routingNumber": { "type": "string" } }, "required": ["type", "accountNumber", "routingNumber"] } ] } ``` ### `anyOf` example ```yaml anyOf: - title: HasEmail type: object properties: email: type: string format: email required: - email - title: HasPhone type: object properties: phone: type: string required: - phone ``` ## Evaluation of `oneOf` and `anyOf` A goal of JSON Schema is to be able to evaluate if JSON is valid or invalid with the defined schema. **More importantly, the validation outcome should be predictable and unambiguous for API consumers.** ### `oneOf` evaluation - Clear and Predictable From the definition of `oneOf`, it is treated like an exclusive OR (XOR): > Must be valid against *exactly one* of the subschemas ```js ($creditCard && !$bankAccount) || (!$creditCard && $bankAccount) ``` Based on our prior `oneOf` declaration for payment methods, the following JSON would match exactly one schema (CreditCard): ```json { "type": "credit", "cardNumber": "4111111111111111", "cvv": "123" } ``` The discriminator field (`type`) makes it crystal clear which schema applies. Code generators, documentation tools, and API consumers can reliably determine the data structure. **Note on terminology:** We use "discriminator property" to mean any property that helps distinguish between schemas (like our `type` field with enum values). This is different from OpenAPI's formal `discriminator` object, which is optional tooling enhancement. You don't need the specification's `discriminator` feature - just well-designed properties that clearly differentiate your schemas. **Important:** JSON schemas default to `additionalProperties: true`, meaning extra properties are allowed. Without proper discriminating properties or `additionalProperties: false`, seemingly different schemas can both match the same data, breaking `oneOf` validation. What about this JSON that could theoretically match both schemas? ```json { "type": "credit", "cardNumber": "4111111111111111", "cvv": "123", "accountNumber": "123456789", "routingNumber": "987654321" } ``` With the discriminator enum values (`[credit]` vs `[bank]`), this clearly matches only the CreditCard schema because `type: "credit"`. The extra bank properties (`accountNumber`, `routingNumber`) would be allowed as additional properties but don't cause schema collision. However, **without discriminators**, this becomes problematic: ```json { "cardNumber": "4111111111111111", "cvv": "123", "accountNumber": "123456789" } ``` If the schemas lacked discriminator enums, this JSON could match both schemas due to `additionalProperties: true` (the OpenAPI default), making the `oneOf` invalid. **This is why discriminators and explicit `additionalProperties` control are essential.** ### `anyOf` evaluation - Ambiguous and Unpredictable From the definition of `anyOf`, it is treated like an inclusive OR: > Must be valid against *at least one* of the subschemas ```js $hasEmail || $hasPhone ``` **The problem:** When multiple schemas match, which one does the parser use? The behavior is undefined. Based on an `anyOf` declaration for contact methods, all of these JSON examples would be valid: ```json { "email": "user@example.com" } ``` ```json { "phone": "+1-555-0123" } ``` ```json { "email": "user@example.com", "phone": "+1-555-0123" } ``` The last example is problematic: it matches both schemas, but different tools might: - Use the first matching schema - Use the last matching schema - Merge properties from all matching schemas - Fail to generate code predictably **This unpredictability makes `anyOf` unsuitable for most API design scenarios.** ## Valid cases ### Preferred approach: `oneOf` with discriminators **Always start with `oneOf`.** It provides clear, predictable validation where data must conform to exactly one well-defined schema. **Polymorphic types with discriminators:** ```yaml oneOf: - title: Painting type: object properties: artworkType: type: string enum: [painting] artist: type: string medium: type: string dimensions: type: string required: - artworkType - artist - title: Sculpture type: object properties: artworkType: type: string enum: [sculpture] artist: type: string material: type: string weight: type: number required: - artworkType - artist ``` **Different response formats:** ```yaml oneOf: - title: SuccessResponse type: object properties: status: type: string enum: [success] data: type: object required: - status - data - title: ErrorResponse type: object properties: status: type: string enum: [error] message: type: string required: - status - message ``` ### Rare exception: `anyOf` (with trade-offs) **Avoid `anyOf` when possible.** Only consider it when schema redesign with `oneOf` is truly impossible. The few legitimate cases are typically constraint validation rather than structural alternatives: **Password strength validation (acceptable use):** ```yaml title: Password type: string anyOf: - minLength: 8 - pattern: "^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])" - pattern: "^(?=.*[!@#$%^&*])" ``` **❌ Avoid this pattern:** ```yaml # DON'T: Unpredictable structure validation title: Contact type: object properties: name: type: string anyOf: - properties: email: type: string format: email required: - email - properties: phone: type: string required: - phone ``` **✅ Better: Redesign with explicit structure:** ```yaml # DO: Clear, predictable structure title: Contact type: object properties: name: type: string email: type: string format: email phone: type: string anyOf: - required: [email] - required: [phone] ``` Even better, use `oneOf` with contact method types: ```yaml title: Contact type: object properties: name: type: string contactMethod: oneOf: - title: EmailContact type: object properties: type: type: string enum: [email] value: type: string format: email required: [type, value] - title: PhoneContact type: object properties: type: type: string enum: [phone] value: type: string required: [type, value] ``` ## Schema design problems that seem to need `anyOf` Words that indicate you might be reaching for `anyOf` when better schema design is needed: - flexible - optional alternatives - multiple valid forms - extensible ### Problem: Poorly designed `oneOf` structures The following example demonstrates a schema design problem that makes developers think they need `anyOf`: ```yaml # ❌ Poor design - creates false restriction oneOf: - type: object properties: email: type: string format: email required: - email - type: object properties: phone: type: string required: - phone ``` This schema rejects users who have both email and phone, which seems wrong. **❌ Don't default to `anyOf`:** ```yaml # AVOID: Unpredictable parsing behavior anyOf: - type: object properties: email: type: string format: email required: - email - type: object properties: phone: type: string required: - phone ``` **✅ Better: Redesign the schema structure:** ```yaml # DO: Explicit, predictable structure type: object properties: email: type: string format: email phone: type: string anyOf: - required: [email] - required: [phone] ``` **✅ Best: Use discriminated union:** ```yaml # BEST: Clear, unambiguous structure type: object properties: preferredContact: oneOf: - title: EmailPreference type: object properties: method: type: string enum: [email] email: type: string format: email required: [method, email] - title: PhonePreference type: object properties: method: type: string enum: [phone] phone: type: string required: [method, phone] # Optional additional contact methods alternateEmail: type: string format: email alternatePhone: type: string ``` ### Conflicting property definitions (applies to `allOf`, not `oneOf`) Note that having the same property with different types is **perfectly valid** for `oneOf`: ```yaml # ✅ Valid oneOf - value can be string OR number oneOf: - type: object properties: value: type: string - type: object properties: value: type: number ``` This works because `oneOf` requires exactly one match: - If `value` is a string, it matches the first schema only - If `value` is a number, it matches the second schema only **This would be illogical for `allOf`:** ```yaml # ❌ Illogical for allOf - nothing can be both string AND number allOf: - type: object properties: value: type: string - type: object properties: value: type: number ``` The `oneOf` version above is valid but could benefit from discriminating properties for better clarity: ```yaml # ✅ Even clearer with discriminating properties oneOf: - type: object properties: type: type: string enum: [text] value: type: string required: - type - value - type: object properties: type: type: string enum: [numeric] value: type: number required: - type - value ``` ### JSON Schemas `additionalProperties` creates unexpected collisions **Critical OpenAPI gotcha:** By default, JSON Schema (and OpenAPI) schemas allow additional properties (`additionalProperties: true`). This means seemingly different schemas can both validate the same JSON, breaking `oneOf`. ```yaml # ❌ These schemas will collide due to additionalProperties: true (default) oneOf: - title: User type: object properties: name: type: string email: type: string - title: Product type: object properties: name: type: string price: type: number ``` This JSON would invalidate the `oneOf` because it matches **both** schemas: ```json { "name": "Widget", "email": "contact@example.com", "price": 29.99 } ``` The User schema matches because it has `name` and `email`, and `price` is allowed as an additional property. The Product schema matches because it has `name` and `price`, and `email` is allowed as an additional property. **✅ Solution 1: Use `additionalProperties: false`** ```yaml oneOf: - title: User type: object properties: name: type: string email: type: string additionalProperties: false - title: Product type: object properties: name: type: string price: type: number additionalProperties: false ``` **✅ Solution 2: Use discriminator properties** ```yaml oneOf: - title: User type: object properties: type: type: string enum: [user] name: type: string email: type: string required: [type] additionalProperties: false - title: Product type: object properties: type: type: string enum: [product] name: type: string price: type: number required: [type] additionalProperties: false ``` **Always be explicit about `additionalProperties` when using `oneOf`.** ### Overlapping schemas that break `oneOf` The following example shows a truly illogical `oneOf` where two schemas can both match the same data: ```yaml # ❌ Illogical - both schemas could match the same object oneOf: - type: object properties: name: type: string age: type: number required: [name] - type: object properties: name: type: string email: type: string required: [name] ``` This JSON would be **invalid** because it matches both schemas: ```json { "name": "John Smith", "age": 30, "email": "john@example.com" } ``` Both schemas match because: - First schema: has required `name` (✓) and `age` is allowed as additional property - Second schema: has required `name` (✓) and `email` is allowed as additional property **Even this simpler case is invalid (often missed by developers):** ```json { "name": "John Smith", "email": "john@example.com" } ``` Developers often think "this clearly matches only the second schema because it has `email`", but it actually matches **both**: - First schema: has required `name` (✓) and `email` is allowed as additional property - Second schema: has required `name` (✓) and `email` property (✓) Both cases violate `oneOf`'s requirement of matching exactly one schema. ### Missing discriminators in `oneOf` When using `oneOf` with similar schemas, always include discriminating properties: ```yaml # ❌ Ambiguous - both schemas could match the same data oneOf: - type: object properties: title: type: string yearCreated: type: number - type: object properties: title: type: string acquisitionPrice: type: number ``` ```yaml # ✅ Clear discrimination oneOf: - type: object properties: recordType: type: string enum: [artwork] title: type: string yearCreated: type: number required: - recordType - type: object properties: recordType: type: string enum: [acquisition] title: type: string acquisitionPrice: type: number required: - recordType ``` ## Summary **Prefer `oneOf` for predictable, unambiguous API schemas.** Always include discriminating properties (like `type` fields with enum values) to ensure clear validation outcomes. **Avoid `anyOf` for structural validation.** It creates parsing ambiguity and unpredictable tool behavior. Instead, redesign your schemas to use `oneOf` with proper discriminating properties. The rare valid uses of `anyOf` are for constraint validation (like password rules), not structural alternatives. Be aware of schema design anti-patterns: - Reaching for `anyOf` when the real problem is poor schema structure - Missing discriminating properties that create ambiguous `oneOf` schemas - Forgetting that OpenAPI's default `additionalProperties: true` causes schema collisions - Conflicting property definitions that make validation impossible **When you think you need `anyOf`, step back and redesign your schema structure.** Clear, discriminated unions with `oneOf` provide better developer experience and tool support. The `oneOf` keyword may optionally use [OpenAPI's discriminator object](/learn/openapi/discriminator) for enhanced tooling support, but simple discriminating properties with enum values work perfectly well. Both `oneOf` and discriminated unions work best with [reference objects](/learn/openapi/ref-guide). Remember: **Predictable schemas lead to better APIs.** Choose clarity over perceived flexibility.