Skip to main content

Integration

Complex data types

oneOf, anyOf, allOf, discriminated unions, nested objects, arrays, and recursive types — how NookDocs parses and renders every JSON Schema composition keyword your spec throws at it.

Production OpenAPI specs rarely have flat schemas. A POST /payments request might accept EITHER a CardPayment OR a BankTransfer. A User response might be the union of a Profile plus a Permissions object. NookDocs parses oneOf / anyOf / allOf / discriminator / nested refs / recursive structures and renders each as a drill-down in the API reference page.

What gets parsed

The parser (src/lib/openapi/parser.ts) walks every schema node and captures the composition keywords verbatim. What NookDocs supports:

oneOf

Exactly one of N variants. Renders as a variant picker in the request/response tab. Each variant expands independently.

anyOf

One or more of N variants. Same renderer as oneOf, different semantic tag.

allOf

Composition — the effective shape is the intersection of all entries. Rendered inline as a merged property table.

discriminator

Typed unions — type: "card" | "bank" decides which variant schema applies. Picker switches the visible schema when the discriminator value changes.

oneOf / anyOf

Use when a field can be one of several shapes. Example: a payment method that's either a card or a bank transfer.

PaymentMethod:
  oneOf:
    - $ref: '#/components/schemas/CardPayment'
    - $ref: '#/components/schemas/BankTransfer'

Rendered as a tab picker in the endpoint page — reader clicks a tab, sees that variant's full field list, request example, and response shape. Behind the scenes the parser emits:

{
  "oneOf": [
    { "type": "object", "properties": { "cardNumber": ..., "cvv": ... } },
    { "type": "object", "properties": { "iban": ..., "swift": ... } }
  ]
}

anyOf renders identically but is semantically different — validators allow matching more than one variant. NookDocs shows the tabs the same way; documenting the semantic difference is left to your per-variant description.

With a discriminator (recommended)

If your variants share a discriminator field, declare it:

PaymentMethod:
  oneOf:
    - $ref: '#/components/schemas/CardPayment'
    - $ref: '#/components/schemas/BankTransfer'
  discriminator:
    propertyName: type
    mapping:
      card: '#/components/schemas/CardPayment'
      bank: '#/components/schemas/BankTransfer'

CardPayment:
  type: object
  properties:
    type: { type: string, enum: [card] }
    cardNumber: { type: string }
    cvv: { type: string, minLength: 3, maxLength: 4 }

BankTransfer:
  type: object
  properties:
    type: { type: string, enum: [bank] }
    iban: { type: string }
    swift: { type: string }

The playground's Try-It panel uses the discriminator to pre-fill the correct variant when the reader picks one — they don't have to know internal schema names.

allOf — composition

Use when a type extends another. Example: AdminUser is User plus admin-specific fields.

User:
  type: object
  required: [id, email]
  properties:
    id: { type: string, format: uuid }
    email: { type: string, format: email }

AdminUser:
  allOf:
    - $ref: '#/components/schemas/User'
    - type: object
      required: [role]
      properties:
        role: { type: string, enum: [admin, superadmin] }
        permissions:
          type: array
          items: { type: string }

NookDocs renders AdminUser as one merged table: id, email, role, permissions. The required arrays union. The allOf structure collapses — readers see a single shape, not "this plus that".

Nested objects

Order:
  type: object
  properties:
    id: { type: string }
    customer:
      type: object
      properties:
        id: { type: string }
        address:
          type: object
          properties:
            street: { type: string }
            city: { type: string }
            country: { type: string, enum: [US, GB, DE, TR] }

Each nested object renders as an expandable row in the schema tree. Readers click customer to drill into its fields, click address to drill further. Recursion is bounded — self-referencing types (a Comment with replies: Comment[]) render with a "See Comment definition" link instead of infinite expansion.

Arrays

Items schema drives the array's visible shape:

Tags:
  type: array
  items:
    type: string
    minLength: 1
    maxLength: 32

Users:
  type: array
  items:
    $ref: '#/components/schemas/User'

Primitive arrays (string[], number[]) render as [string] in the type column. Object arrays show the item shape below, drill-expandable.

Enums

Status:
  type: string
  enum: [pending, active, archived]

Rendered as a pill-group next to the field. Each value clickable (future: links to a tag-filtered view of endpoints that handle that status — not shipped yet).

Recursive types

Self-referencing types are preserved:

Comment:
  type: object
  properties:
    id: { type: string }
    text: { type: string }
    replies:
      type: array
      items:
        $ref: '#/components/schemas/Comment'

The parser detects the cycle and renders replies: Comment[] as a link-back to the schema header instead of infinite expansion. Prevents stack overflow on user-authored recursive schemas.

Nullable fields

OpenAPI 3.0 uses nullable: true; OpenAPI 3.1 uses type: ["string", "null"]. Both work:

# 3.0 style
deletedAt:
  type: string
  format: date-time
  nullable: true

# 3.1 style
deletedAt:
  type: [string, "null"]
  format: date-time

Rendered as string | null in the type column.

Format hints

NookDocs passes every OpenAPI format through — uuid, email, uri, date-time, ipv4, hostname, binary, byte, password. Formats show next to the type. The playground pre-fills examples based on format (valid UUID for uuid, user@example.com for email, etc.).

id: { type: string, format: uuid }
createdAt: { type: string, format: date-time }
password: { type: string, format: password, minLength: 12 }

Examples

example and examples on any schema node render as highlighted sample values:

CardPayment:
  type: object
  properties:
    cardNumber:
      type: string
      example: "4242424242424242"
    cvv:
      type: string
      example: "123"
  example:
    cardNumber: "4242424242424242"
    cvv: "123"

Schema-level example wins over per-property example in the request panel preview. Use both — property examples help readers scan field by field; schema example is the paste-ready whole request body.

Constraints that actually render

All standard JSON Schema validation keywords pass through to the field description:

  • minLength / maxLength — string bounds

  • minimum / maximum / exclusiveMinimum / exclusiveMaximum — number bounds

  • minItems / maxItems / uniqueItems — array constraints

  • pattern — regex, rendered as inline code

  • default — shown in a "Default:" suffix next to the type

cvv:
  type: string
  minLength: 3
  maxLength: 4
  pattern: '^[0-9]+$'

Renders with min 3, max 4, pattern: ^[0-9]+$ in the description slot.

What's not parsed yet

  • Conditional schemas (if / then / else from JSON Schema 2019-09+) — ignored; the parser walks past them. Use oneOf + discriminator as an alternative.

  • $dynamicRef / $dynamicAnchor — specific to JSON Schema 2019-09+ vocabulary; not resolved. Static $ref works fine.

  • Custom formats registered via format-assertion — passed through verbatim as a string. Render engine doesn't validate.

Related

Was this page helpful?

Last updated August 7, 2026

Complex data types | NookDocs | NookDocs