Ratgeber · JSON Schema 2020-12
Rekursive JSON-Schemas mit $ref: Tree-Strukturen sauber validieren
$defs für lokale Definitionen, $ref für die Referenzen. JSON-Pointer-Syntax #/$defs/node. recursive-Ref-Resolution und Performance-Tipps.
Von Mateusz Viola
Betreiber & redaktionelle Verantwortung json-validator.de
Veröffentlicht
Aktualisiert:
Rekursive JSON-Strukturen - typische Fälle
Manche Datenstrukturen referenzieren sich selbst:
- Kommentar-Threads (Kommentar hat eine Liste von Antworten, die selbst Kommentare sind)
- Dateibaum / Sitemap-Hierarchie
- Verschachtelte Kategorien
- Linked-List-artige AST-Knoten
Solche Strukturen werden in JSON Schema mit $ref und $defs beschrieben.
$defs vs. definitions
In Draft 04-07: das Keyword hieß definitions. In Draft 2019-09+ heißt es $defs. Beide haben identische Semantik. Für neue Projekte: $defs.
Minimal-Beispiel: Tree-Knoten
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/tree.schema.json",
"$defs": {
"Node": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string" },
"children": {
"type": "array",
"items": { "$ref": "#/$defs/Node" }
}
}
}
},
"$ref": "#/$defs/Node"
}
Passt zu:
{
"name": "root",
"children": [
{ "name": "child1", "children": [] },
{ "name": "child2", "children": [
{ "name": "grandchild" }
]}
]
}
JSON-Pointer-Syntax
$ref nimmt einen JSON-Pointer. Aufbau:
#bedeutet "das aktuelle Dokument"/trennt Pfad-Segmente/$defs/Nodeverweist auf das Feld $defs/Node im aktuellen Dokument
Externe Refs sind auch möglich:
{ "$ref": "https://example.com/address.schema.json" }
{ "$ref": "./common.schema.json#/$defs/Address" }
Ajv und Schema-Compilation
Ajv löst $refs zur Compile-Zeit auf. Bei vielen externen Refs muss er die Schemas vorladen:
const ajv = new Ajv({ loadSchema: async (uri) => {
return await fetchSchema(uri);
}});
const validate = await ajv.compileAsync(rootSchema);
recursive-Ref-Pitfalls
Bei rekursiven Schemas gibt es zwei Klassen von Problemen:
1. Endlose Validation-Loops
Vermieden durch Ajv automatisch - der Validator merkt sich gerade besuchte Schemas und bricht ab.
2. $ref auf $defs nicht möglich vom Root
Wenn das Root-Schema selbst rekursiv sein soll, geht das wie im Beispiel oben (Root ist nur ein $ref auf $defs/Node). Direkter Selbst-Ref ist nicht erlaubt.
$dynamicRef / $dynamicAnchor (Draft 2020-12)
Für fortgeschrittene Use-Cases wie generische Container-Schemas:
{
"$id": "https://example.com/list.schema",
"$defs": {
"Item": { "$dynamicAnchor": "Item", "type": "object" }
},
"type": "array",
"items": { "$dynamicRef": "#Item" }
}
$dynamicRef erlaubt "Override" der Definition durch ein Schema das das List-Schema erweitert. Selten gebraucht - wenn unklar, normales $ref nutzen.
Performance bei tiefer Rekursion
Ajv ist schnell, aber tiefe rekursive Strukturen (>100 Levels) brauchen entsprechend Zeit. Maßnahmen:
- maxItems / maxProperties auf den Children-Arrays setzen
- Tiefe per externem Check vorher prüfen
- Strukturen flach halten wo möglich
Im Tool testen
Das Schema oben + ein verschachteltes Payload können oben getestet werden. Probiere bewusst tiefe Strukturen - du siehst die Performance-Charakteristik im Browser.