JSON 09 ๐ Structural Reuse โ $ref, $defs, and External Schemas
The $ref keyword is the mechanism that lets a schema reuse another schema instead of duplicating it. The value of $ref is a URI reference, and the validator resolves it against the current base URI to find the referenced schema, then applies that schema to the instance location where the $ref appears . The reference can point to a subschema within the same documentโusing a JSON Pointer like #/$defs/addressโor to a schema in another document, identified by its $id . The reuse is the mechanism that keeps the schema maintainable, because a change to the referenced schema propagates to every place that references it.
The $defs keyword is the standard container for the reusable subschemas. It does not affect the validation result on its own; it is a location where the schema author can place the subschemas that the $ref keywords point to . The $id keyword is the identifier that makes a schema referenceable. The root schema SHOULD have an absolute $id, and the embedded schema resources can have their own $id to become referenceable as separate resources . The combination of the threeโthe $id to identify, the $defs to store, and the $ref to reuseโis the structural reuse mechanism.
Key point: $ref references a schema by its URI, resolved against the base URI. $defs is the container for the reusable subschemas, and it does not affect validation by itself. $id gives a schema its identifier, and an embedded schema with its own $id becomes a separate schema resource. The validator resolves the $ref against the base URI, and the external schemas must be pre-loaded or registered before the referencing schema is compiled.
Why the structural reuse exists
The duplication problem. A schema that describes a customer with a shipping address and a billing address would duplicate the address schema in both places if the $ref did not exist. The duplication is the maintenance burden, and the $ref is the mechanism that removes it. The address schema is defined once, and the two properties reference it .
The recursive problem. A tree structureโa person with an array of children, each of which is a personโneeds a schema that references itself. The $ref is the mechanism that creates the recursion, and the recursion is the reason the $ref exists in the first place . The recursion is allowed, and the infinite loop is the disallowed case.
The modularity problem. A large schema can be split into the smaller files, each with its own $id, and the files can be developed and tested independently. The $ref is the mechanism that connects the files, and the connection is the reason the schema can be maintained as a set of modules rather than a single monolith.
The resolution problem. The $ref is resolved against the base URI, which is the $id of the nearest parent schema or the root. A relative reference like other.json resolves to https://example.net/other.json if the base URI is https://example.net/root.json . The resolution is the mechanism that makes the references portable, and the base URI is the anchor.
The loading problem. The validator does not automatically download the external schemas from the network. The schemas must be pre-loaded or registered with the validator before the referencing schema is compiled . The $id is the identifier, not necessarily the network location, and the validator is expected to work offline by default.
a. The internal references and the $defs
The internal reference points to a subschema within the same document. The JSON Pointer is the fragment identifier, and the $defs is the conventional container.
// The internal reference with the $defs
{
"$id": "https://example.com/schemas/customer",
"type": "object",
"properties": {
"first_name": { "type": "string" },
"last_name": { "type": "string" },
"shipping_address": { "$ref": "#/$defs/address" },
"billing_address": { "$ref": "#/$defs/address" }
},
"required": ["first_name", "last_name", "shipping_address", "billing_address"],
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" }
},
"required": ["street", "city", "state"]
}
}
}
The #/$defs/address is the JSON Pointer that resolves to the address subschema. The two properties reference the same subschema, and the schema is defined once . The $defs does not affect the validation on its own, and the $ref is the mechanism that applies the referenced schema to the instance.
The $anchor keyword is the alternative to the JSON Pointer. It defines a plain-name fragment that is independent of the schema structure, and the reference uses the anchor name instead of the JSON Pointer path .
// The $anchor
{
"$id": "https://example.com/schemas/root",
"items": { "$ref": "#item" },
"$defs": {
"single": {
"$anchor": "item",
"type": "object"
}
}
}
The #item is the plain-name fragment, and it resolves to the subschema with the matching $anchor. The anchor is the mechanism that keeps the reference stable when the schema is restructured, because the anchor is not tied to the JSON Pointer path.
b. The external references and the $id
The external reference points to a schema in another document. The $id of the target schema is the identifier, and the reference uses that identifier.
// The external reference
{
"$id": "https://example.com/schemas/customer",
"type": "object",
"properties": {
"shipping_address": { "$ref": "address" },
"billing_address": { "$ref": "address" }
}
}
// The address schema
{
"$id": "https://example.com/schemas/address",
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" }
}
}
The $ref: "address" resolves against the base URI https://example.com/schemas/customer to https://example.com/schemas/address. The validator uses the schema with that $id if it has been registered . The address schema is a separate file, and the two files are connected by the $id and the $ref.
The Ajv validator registers the external schemas with the addSchema method or the schemas option. The $id is the identifier, and the validator does not download the schema from the network unless the compileAsync method is used .
// The Ajv registration
import Ajv from "ajv";
const ajv = new Ajv();
ajv.addSchema(addressSchema); // The address schema is registered.
const validate = ajv.compile(customerSchema); // The customer schema is compiled.
The $id does not have to be a network location. It is an identifier, and the actual file can be stored anywhere. The validator uses the $id to look up the schema in its registry, not to fetch it from the URL .
Complete Example Session
// ============================================
// PART 1: THE INTERNAL REFERENCE
// ============================================
{
"$id": "https://example.com/schemas/customer",
"type": "object",
"properties": {
"shipping_address": { "$ref": "#/$defs/address" }
},
"$defs": {
"address": {
"type": "object",
"properties": { "street": { "type": "string" } }
}
}
}
// The #/$defs/address is the JSON Pointer.
// The address schema is defined once.
// ============================================
// PART 2: THE ANCHOR
// ============================================
{
"$id": "https://example.com/schemas/root",
"items": { "$ref": "#item" },
"$defs": {
"single": {
"$anchor": "item",
"type": "object"
}
}
}
// The #item is the plain-name fragment.
// The anchor is independent of the structure.
// ============================================
// PART 3: THE EXTERNAL REFERENCE
// ============================================
{
"$id": "https://example.com/schemas/customer",
"properties": {
"shipping_address": { "$ref": "address" }
}
}
// The address resolves to https://example.com/schemas/address.
// ============================================
// PART 4: THE EXTERNAL SCHEMA
// ============================================
{
"$id": "https://example.com/schemas/address",
"type": "object",
"properties": { "street": { "type": "string" } }
}
// The address schema is a separate file.
// The $id is the identifier.
// ============================================
// PART 5: THE AJV REGISTRATION
// ============================================
import Ajv from "ajv";
const ajv = new Ajv();
ajv.addSchema(addressSchema);
const validate = ajv.compile(customerSchema);
// The address schema is registered before the customer schema.
// ============================================
// PART 6: THE RECURSIVE REFERENCE
// ============================================
{
"$id": "https://example.com/schemas/person",
"type": "object",
"properties": {
"name": { "type": "string" },
"children": {
"type": "array",
"items": { "$ref": "#" }
}
}
}
// The # refers to the root schema.
// The recursion is allowed.
// ============================================
// PART 7: THE BUNDLING
// ============================================
// The bundle embeds the referenced schemas in the $defs.
{
"$id": "https://example.com/schemas/customer.bundle",
"$ref": "https://example.com/schemas/customer",
"$defs": {
"https://example.com/schemas/customer": { /* ... */ },
"https://example.com/schemas/address": { /* ... */ }
}
}
// The bundle is a single document.
// The $defs keys are the resolved URIs.
// ============================================
// PART 8: THE BASE URI RESOLUTION
// ============================================
// The base URI is the nearest parent $id.
// The relative reference resolves against it.
// The other.json resolves to https://example.net/other.json.
// ============================================
// PART 9: THE DYNAMIC REFERENCE
// ============================================
// The $dynamicRef is the 2020-12 mechanism.
// The $dynamicAnchor is the anchor.
// The dynamic reference is for the extensible recursive schemas.
// ============================================
// PART 10: THE CHECKLIST
// ============================================
// [ ] The $id is set on the root schema.
// [ ] The $defs contains the reusable subschemas.
// [ ] The $ref uses the JSON Pointer or the anchor.
// [ ] The external schemas are registered before the compile.
// [ ] The recursion uses the # or the named anchor.
// [ ] The bundling is used for the distribution.
The ten parts covered the internal reference, the anchor, the external reference, the external schema, the Ajv registration, the recursive reference, the bundling, the base URI resolution, the dynamic reference, and the checklist. The through-line is that the $ref is the reuse mechanism, and the $id is the identifier.
Quick Reference
The reuse keywords
| Keyword | Purpose |
|---|---|
$ref | References a schema by its URI |
$defs | The container for the reusable subschemas |
$id | The schema identifier and the base URI |
$anchor | The plain-name fragment identifier |
The reference types
| Type | Example | Resolution |
|---|---|---|
| The internal JSON Pointer | #/$defs/address | The same document |
| The internal anchor | #item | The same document |
| The external schema | address | The base URI resolution |
| The recursive | # | The root schema |
The validator registration
| Validator | Method | Purpose |
|---|---|---|
| Ajv | addSchema(schema) | Register the external schema |
| Ajv | compile(schema) | Compile the referencing schema |
| Ajv | getSchema(id) | Retrieve the compiled schema |
The bundling
| Aspect | Detail |
|---|---|
| The purpose | A single document for the distribution |
| The mechanism | The $defs with the resolved URIs |
The $ref | The top-level reference to the root |
| The support | The static $ref only |
Best Practices
โ Do This:
// Use the $defs for the reusable subschemas
{ "$defs": { "address": { "type": "object" } } } // โ
// Use the $ref for the reuse
{ "shipping_address": { "$ref": "#/$defs/address" } } // โ
// Set the $id on the root schema
{ "$id": "https://example.com/schemas/customer" } // โ
โ Don’t Do This:
// Reference a schema by its file path
{ "$ref": "./address.json" } // the file path is not the identifier // โ
// Assume the validator downloads the external schema
// The schema must be registered. // โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| The external reference is not resolved | The schema is not registered | Use addSchema |
| The relative reference resolves to the wrong URI | The base URI is missing | Set the $id on the root |
The $ref with the file path fails | The file path is not the identifier | Use the $id |
| The recursion is an infinite loop | The $ref refers to another $ref | Use the direct recursion |
Real-World Examples
1. The internal reference
{ "$ref": "#/$defs/address" }
2. The anchor
{ "$ref": "#item" }
3. The external reference
{ "$ref": "address" }
4. The external schema
{ "$id": "https://example.com/schemas/address", "type": "object" }
5. The Ajv registration
ajv.addSchema(addressSchema);
6. The recursive reference
{ "items": { "$ref": "#" } }
7. The bundling
{ "$ref": "https://example.com/schemas/customer", "$defs": { "https://example.com/schemas/customer": {} } }
8. The base URI
{ "$id": "https://example.com/schemas/customer" }
9. The dynamic reference
{ "$dynamicRef": "#item" }
10. The $defs
{ "$defs": { "address": { "type": "object" } } }
Visual
The Reuse Mechanism
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE SCHEMA REUSE โ
โ โ
โ customer.json โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ $ref: "#/$defs/ โ โ
โ โ address" โ โ
โ โโโโโโโโโโโโฌโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ $defs: โ The reusable subschema โ
โ โ address: {...} โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ The address schema is defined once and used twice. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The External Reference
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE EXTERNAL SCHEMA โ
โ โ
โ customer.json address.json โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ $id: .../customer โ โ $id: .../address โ โ
โ โ $ref: "address" โโโโถโ type: object โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ The $ref resolves to the $id of the address schema. โ
โ The address schema must be registered with the validator. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The Bundling
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE BUNDLE โ
โ โ
โ customer.bundle.json โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ $id: .../bundle โ โ
โ โ $ref: .../customer โ โ
โ โ $defs: โ โ
โ โ .../customer: {} โ The root schema โ
โ โ .../address: {} โ The referenced schema โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ The bundle is a single document with the embedded schemas. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
$ref | References a schema by its URI |
$defs | The container for the reusable subschemas |
$id | The schema identifier and the base URI |
| The internal reference | The JSON Pointer or the anchor |
| The external reference | The $id of the target schema |
| The recursion | The # or the named anchor |
| The bundling | The single document with the embedded schemas |
Key takeaways:
$refreferences a schema by its URI. The value is resolved against the base URI, and the validator applies the referenced schema to the instance location where the$refappears . The internal reference uses the JSON Pointer or the anchor, and the external reference uses the$idof the target schema.$defsis the container for the reusable subschemas. It does not affect the validation on its own, and the$refis the mechanism that applies the subschemas to the instance . The$defsis the standard location for the reusable definitions, and the schema is more readable when the complex parts are defined there.$idis the schema identifier and the base URI. The root schema SHOULD have an absolute$id, and the relative references resolve against it . The$iddoes not have to be a network location, and the validator uses it to look up the schema in the registry.- The external schemas must be registered before the compile. The validator does not automatically download the external schemas, and the
addSchemais the mechanism that registers them . The$idis the identifier, not necessarily the network location. - The bundling is the distribution mechanism. The bundle is a single document with the root schema and the referenced schemas embedded in the
$defs. The$defskeys are the resolved URIs, and the bundle is the self-contained artifact.
Remember: The structural reuse is the $ref pointing to the $defs or the external schema. The $id is the identifier, and the $anchor is the plain-name fragment. The validator resolves the $ref against the base URI, and the external schemas must be registered. The bundling is the single document for the distribution, and the recursion is the # or the named anchor.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!