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-timeRendered 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 boundsminimum/maximum/exclusiveMinimum/exclusiveMaximum— number boundsminItems/maxItems/uniqueItems— array constraintspattern— regex, rendered as inline codedefault— 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/elsefrom JSON Schema 2019-09+) — ignored; the parser walks past them. UseoneOf+discriminatoras an alternative.$dynamicRef/$dynamicAnchor— specific to JSON Schema 2019-09+ vocabulary; not resolved. Static$refworks fine.Custom formats registered via
format-assertion— passed through verbatim as a string. Render engine doesn't validate.
Related
OpenAPI overview — how specs land in the repo and get parsed
Playground — the Try-It panel that consumes these schemas
Multiple responses — complementary — different status codes get different schemas
Manual API pages — escape hatch for schemas the parser can't represent