json-validator.de

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.

Foto von Mateusz Viola

Von Mateusz Viola

Betreiber & redaktionelle Verantwortung json-validator.de

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/Node verweist 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.

Mehr zum Thema