Skip to content

Transport JSON Schema Export

json-rest-schema can also export a transport-facing JSON Schema document for adapters that want pre-handler validation.

javascript
const userSchema = createSchema({
  id: { type: 'id', required: true },
  email: { type: 'string', required: true },
  age: { type: 'number', min: 18, defaultTo: 18 },
  status: { type: 'string', enum: ['draft', 'published'] }
})

const createTransportSchema = userSchema.toJsonSchema()
const patchTransportSchema = userSchema.toJsonSchema({ operation: 'patch' })

Key points:

  • Draft: exports draft-07 JSON Schema.
  • Operation-aware: operation: '<name>' controls the required list and whether defaultTo is emitted.
  • Compatibility: mode: 'create' | 'replace' | 'patch' still works as shorthand for the built-in operations.
  • Transport-facing: the export is intended for JSON/Ajv/Fastify-style request validation, not for reproducing every in-process coercion path.
  • Strict field shape: additionalProperties defaults to false because runtime validation rejects unknown fields. Override it with toJsonSchema({ additionalProperties: true }) if needed.
  • Nested export: schema-backed nested object contracts are hoisted into draft-07 definitions and referenced with $ref, so repeated and recursive graphs stay finite. Nested object fields inherit the active operation, while object schemas used as array items or object-map values are exported in replace mode.
  • Recursive graph support: runtime validation and toJsonSchema() both support self-recursive schema graphs. Direct self-recursive object fields point back to #, while recursive nested contracts are emitted through definitions.
  • Opaque bags stay opaque: type: 'object' plus additionalProperties: true exports as a permissive object field and does not invent child property rules.
  • Single source of truth: only rules owned by json-rest-schema are exported. External metadata keys from other layers are ignored.
  • Passive metadata preserved: schema-owned passive metadata such as precision, scale, unsigned, and temporalPrecision is preserved under x-json-rest-schema.metadata.
  • Custom rules: if a custom type or validator needs transport export support, attach a toJsonSchema() hook to the handler. If you register a custom validator without that hook, export fails loudly instead of silently drifting.

Worked nested export example

javascript
const schema = createSchema({
  workspace: {
    type: 'object',
    required: true,
    schema: workspaceSummarySchema
  },
  roles: {
    type: 'array',
    items: roleSchema
  },
  metadata: {
    type: 'object',
    additionalProperties: true
  }
})

Exporting schema.toJsonSchema() gives you:

  • workspace as a $ref to a definition whose required fields still inherit the active operation
  • roles.items as a $ref to a nested object definition exported in replace mode
  • metadata as { type: 'object', additionalProperties: true }
  • object maps as { type: 'object', additionalProperties: <value schema> }
  • passthrough nested objects as validated properties plus additionalProperties: true

That means the transport export stays aligned with runtime semantics:

  • nested objects behave like nested contracts even when the runtime schema graph is recursive
  • array object items behave like complete replacements
  • opaque bags stay opaque instead of pretending to be structured

Worked recursive export example

Using the same recursive nodeSchema from the Recursive Schemas chapter above:

javascript
const transportSchema = nodeSchema.toJsonSchema()

Key recursive export behaviors:

  • a direct self-recursive object field such as parent becomes a reference back to #
  • recursive nested contracts reached through array items or dynamic object values are hoisted into definitions
  • the exporter stays finite because it tracks schema graph nodes, not just call depth

For the parent field above, the transport shape is:

javascript
{
  allOf: [
    {
      $ref: '#'
    }
  ],
  'x-json-rest-schema': {
    castType: 'object'
  }
}

For the children.items edge, the transport shape becomes a $ref into definitions, and that referenced definition points back to itself for deeper children.items recursion.


GPL-3.0-only