C.W.K.
Stream
Lesson 06 of 08 · published

Composition — allOf, oneOf, anyOf, not

~12 min · json-schema, composition, polymorphism

Level 0Plaintext
0 XP0/64 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

Combining schemas to model real-world shapes

allOf — intersection

The instance must validate against every sub-schema. Use this for inheritance-like composition: 'is a Base AND has these extra fields.'

anyOf — union (at least one)

The instance must validate against at least one sub-schema. Use this for fuzzy/loose unions: 'string or number,' 'one of several optional shapes.'

oneOf — exclusive union (exactly one)

The instance must validate against exactly one sub-schema. Use this for tagged unions: 'either a payment with credit_card details OR a payment with bank_transfer details, never both.'

not — negation

The instance must NOT validate against the sub-schema. Use sparingly — error messages on not are notoriously vague. Better to express the constraint positively when possible.

Tagged-union pattern: for polymorphic data ('one of N variants'), prefer oneOf with a discriminating field (kind, type) checked via const. Validators give clear error messages when one variant matches; anyOf obscures which variant the user intended.

Code

allOf — composing a base with extras·json
{
  "$defs": {
    "Identifiable": {
      "type": "object",
      "properties": { "id": { "type": "integer" } },
      "required": ["id"]
    }
  },
  "allOf": [
    { "$ref": "#/$defs/Identifiable" },
    {
      "type": "object",
      "properties": {
        "name": { "type": "string" }
      },
      "required": ["name"]
    }
  ]
}
oneOf — tagged union (clean error messages)·json
{
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "kind":       { "const": "credit_card" },
        "card_last4": { "type": "string", "pattern": "^[0-9]{4}$" }
      },
      "required": ["kind", "card_last4"]
    },
    {
      "type": "object",
      "properties": {
        "kind":          { "const": "bank_transfer" },
        "routing_number": { "type": "string" },
        "account_last4":  { "type": "string", "pattern": "^[0-9]{4}$" }
      },
      "required": ["kind", "routing_number", "account_last4"]
    }
  ]
}
anyOf — string or number-as-string·json
{
  "properties": {
    "id": {
      "anyOf": [
        { "type": "integer" },
        { "type": "string", "pattern": "^[0-9]+$" }
      ]
    }
  }
}

External links

Exercise

Model a 'payment method' as a oneOf with three variants (credit_card / bank_transfer / wallet), each with its own required fields, all sharing a kind discriminator. Validate one document of each kind. Then write one document with two kinds' fields mixed and watch the validator reject it (no branch matches exactly).

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.