6 / 6

Lesson 6: Real-World Schema

Schema Editor
Data Editor
Validation Result

Type in the editors to see validation results.

$defs$refnested objectsfull API response

"$defs" lets you define reusable sub-schemas in one place, avoiding repetition when the same shape appears in multiple parts of your schema. The "$ref" keyword references one of those definitions using a JSON Pointer like "#/$defs/Address". This composability is essential when building schemas for real-world API responses that share common structures such as address, line items, or user profiles. Putting it all together produces a self-documenting schema that is easy to maintain as your data model evolves.

Why This Matters

Production APIs rarely have one flat shape — an order response reuses the same Address shape for shipping and billing, a User object shows up in dozens of endpoints, and a Money type needs the same currency+amount validation everywhere. Without $defs and $ref, teams end up copy-pasting the same nested schema in ten places, and when the shape changes (adding a "country" field, say), nine of those copies quietly go stale. $defs/$ref keep a single definition as the source of truth, and this same pattern is exactly how OpenAPI's "components/schemas" and generated SDK types work — learning it here transfers directly to designing real API contracts.

Common Mistakes

  • Assuming "$ref" can point straight to another file without any setup.A bare "$ref": "./address.schema.json" requires the validator to resolve external files (or a base URI), which many simple validators — including browser-based playgrounds — don't support. Internal references like "#/$defs/Address" always work because they resolve within the same document.
  • Defining a shape in "$defs" and forgetting to actually reference it anywhere.An unused entry under $defs validates nothing on its own — it's just a dictionary of reusable pieces. Only properties that use $ref to point at it get validated against that shape.
  • Copy-pasting the same nested object schema in multiple places instead of extracting it to $defs.Duplication drifts: when the Address shape needs a new field, every inline copy has to be updated by hand. Extract anything reused more than once into $defs and reference it with $ref.
  • Mixing up Draft-07's "definitions" keyword with Draft 2020-12's "$defs".Older schemas use "definitions" and reference it as "#/definitions/Address". Draft 2020-12 renamed it to "$defs" ("#/$defs/Address") — both work only if the $ref path matches the keyword actually used in that document.

Challenge

Add a billingAddress field (same shape as shippingAddress) using $ref: '#/$defs/Address'.

Frequently Asked Questions

Yes, in principle — $ref supports absolute and relative URIs to external schema files. In practice, support depends on your validator/tooling; many simple or browser-based validators only resolve references within the same document (like #/$defs/Address), so external refs often need to be bundled into one file first.

Yes — JSON Schema fully supports recursive $ref, for example a Category definition whose "children" property is an array of { "$ref": "#/$defs/Category" }. This is the standard way to model trees and nested comment threads.

They serve the same purpose — a place to define reusable sub-schemas — but the keyword name changed between drafts. Draft-07 and earlier use "definitions" referenced as "#/definitions/Name"; Draft 2019-09 and later use "$defs" referenced as "#/$defs/Name".

Because $defs/$ref mirror how OpenAPI's components/schemas work, a schema built this way maps cleanly onto OpenAPI's schema section, and tools like json-schema-to-typescript (or JSONKit's own JSON Schema Generator and JSON to TypeScript tools) can turn each $defs entry into its own named interface.