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