JSON Toolkit

FAQ zu Abfrage und Validierung: JSONPath und Schema

Fazit: Ob Pfadabfragen JSONPath oder Pointer verwenden sollten, wie man zwischen Patch und Merge Patch wählt, was Schema blockieren kann und wie man eine Draft-Version wählt – alles in einem Durchgang erklärt.

Fortgeschritten~12 Min. LesezeitVeröffentlicht 2026-10-30Aktualisiert 2026-10-30
Titelbild für die FAQ zu Abfrage und Validierung

Kurz gesagt: JSONPath zum Finden von Knoten nach Bedingung, JSON Pointer oder JSON Patch zum Adressieren und Bearbeiten exakter Stellen und Merge Patch zum Zusammenführen von Konfiguration; legen Sie neue Schemas auf 2020-12 fest und aktivieren Sie allErrors, damit jeder Fehlschlag in einem Durchgang zurückkommt.

Abfragen heißt „es finden“; Validierung heißt „bestätigen, dass es richtig ist“. Jedes hat seinen eigenen Standard, mischen Sie sie also nicht.

1. Pfadabfragen

Wie soll ich zwischen JSONPath und JSON Pointer wählen?

Das hängt davon ab, ob Sie „nach Bedingung finden“ oder „nach Position holen“.

Dimension JSONPath JSON Pointer
Standard RFC 9535 (2024) RFC 6901 (2013)
Ausdruck $.users[?(@.age > 18)].id /users/0/id
Platzhalter und Rekursion Unterstützt *, .. Nicht unterstützt
Bedingtes Filtern Unterstützt Nicht unterstützt
Ergebnis Eine Knotenmenge (womöglich mehrere) Eine einzige Stelle
Ausdruckskraft Stark, aber mit schwerem Erbe an Implementierungsunterschieden Schwach, aber vollständig deterministisch
Typische Verwendung Log-Extraktion, Debugging, Datenexploration JSON Patch, Konfigurationsadressierung, Fehlerpfade

Die Auswahlregel ist geradlinig:

  • Sie müssen „alle Knoten holen, die einer Bedingung entsprechen“ → JSONPath;
  • Sie müssen „eine exakte Stelle festnageln und hinzufügen, entfernen oder ändern“ → JSON Pointer;
  • Verwenden Sie sie zusammen: den Knoten mit JSONPath finden und seine Lage mit Pointer beschreiben.

Verwenden Sie JSONPath nicht zur Adressierung in einem API-Vertrag. JSONPath gibt eine Menge zurück, und die drei Zweige für leere, Ein-Element- und Mehr-Element-Ergebnisse machen das Protokoll kompliziert; Pointer bedeutet „entweder es zeigt auf eine existierende Stelle oder es existiert nicht“, was besser zu einem Vertrag passt.

Was ist der Unterschied zwischen $.. und $.*?

$.. ist rekursiver Abstieg und durchläuft jede Ebene; $.* gibt nur die direkten Kinder der aktuellen Ebene zurück.

{
  "a": { "id": 1, "child": { "id": 2 } },
  "b": { "id": 3 }
}
  • $.* → die beiden Objekte {id:1, child:{...}} und {id:3};
  • $..id → drei Werte: 1, 2, 3;
  • $.*.id → 1 und 3, ohne das verschachtelte 2.

Rekursiver Abstieg ist praktisch, aber teuer: $.. durchläuft den ganzen Baum, was in einem großen Dokument O(n) ist, und sammelt leicht tiefe Felder ein, die Sie nicht beabsichtigt haben. Funktioniert eine konkrete Ebene, vermeiden Sie ...

Können Filter Strings vergleichen? Können sie mit Regex matchen?

Stringvergleich funktioniert; Regex hängt von der Implementierung ab.

Standard-Syntax (RFC 9535):

$.items[?(@.status == "active")]
$.items[?(@.price > 10 && @.stock > 0)]
$.items[?(@.name == $.target)]      // compare with another node

Stringvergleich verwendet doppelte Anführungszeichen (einfache sind in RFC 9535 auch erlaubt, aber manche ältere Implementierungen akzeptieren nur doppelte). Numerischer Vergleich folgt numerischer Semantik; einen String mit einer Zahl zu mischen, etwa "10" > 9, ist im Standard undefiniertes Verhalten, verlassen Sie sich also nicht darauf.

Für Regex definiert RFC 9535 die Funktionen match() und search(), aber ihre Unterstützung variiert erheblich zwischen Implementierungen. Der sichere Ansatz: JSONPath grob filtern lassen und das Regex-Matching im Code erledigen, nachdem Sie die Ergebnisse geholt haben.

Sollte ich JSON Patch oder Merge Patch verwenden?

Beide beschreiben „wie man etwas ändert“, aber ihre Ausdruckskraft und Semantik sind völlig verschieden.

Dimension JSON Patch (RFC 6902) Merge Patch (RFC 7386)
Form Ein Array von Operationen [{"op":"add",...}] Ein Teilobjekt
Unterstützte Operationen add / remove / replace / move / copy / test Nur Zusammenführen (Löschen über null)
Kann Array-Elemente adressieren Ja, nach Index Nein, das ganze Array wird ersetzt
Atomarität test ermöglicht optimistische Sperre Keine
Dokumentgröße Groß, aber präzise Klein, aber begrenzt in der Ausdruckskraft
Gut für Feinkörnige Änderungen, partielle API-Updates Konfigurationszusammenführung, partielles Formular-Absenden

Entscheidungskriterien:

  • Ein Feld des dritten Elements in einem Array ändern → JSON Patch;
  • Mehrere Konfigurationseinträge in die bestehende Konfiguration zusammenführen → Merge Patch;
  • Nebenläufigkeitskontrolle „scheitern, wenn der Originalwert nicht X ist“ → JSON Patchs test.

Beachten Sie die Falle in Merge Patchs null-Semantik: null bedeutet „diesen Schlüssel löschen“, nicht „den Wert auf null setzen“. Einen Wert auf null zu setzen ist mit Merge Patch unmöglich.

2. Schema-Validierung

Welche Probleme löst JSON Schema?

Es verwandelt „wie die Daten aussehen sollten“ von einer mündlichen Absprache in einen maschinenprüfbaren Vertrag. Die Fehler, die es tatsächlich blockieren kann:

  • Fehlende Pflichtfelder (required);
  • Falscher Typ, etwa ein String, wo eine Zahl erwartet wird (type);
  • Werte außerhalb des Bereichs (minimum / maximum / minLength);
  • Werte außerhalb der erlaubten Menge (enum);
  • Uneinheitliche Array-Elementtypen oder falsche Array-Länge (items / minItems);
  • Fehlende Felder in verschachtelten Strukturen (rekursive Validierung über properties);
  • Zusätzliche Felder (additionalProperties: false).

Was es nicht lösen kann: Geschäftsregeln („die Endzeit muss später als die Startzeit sein“), feldübergreifende Bedingungen (manche lassen sich mit if/then ausdrücken, aber die Lesbarkeit leidet), Datenbank-Eindeutigkeit und Berechtigungsprüfungen.

Schema ist also der Torwächter für „Datenform“, kein Ersatz für Geschäftslogik.

Sollte ich draft-07 oder 2020-12 verwenden?

Verwenden Sie 2020-12 für neue Projekte; prüfen Sie für bestehende Systeme, was der Validator unterstützt.

Version Status Zentrale Änderungen
draft-04 Veraltet Viele ältere Bibliotheken nehmen es noch als Standard
draft-07 De-facto-Standard Breiteste Unterstützung, reichstes Ökosystem
2019-09 Übergangsweise Führte $defs, unevaluatedProperties ein
2020-12 Aktuell empfohlen items in prefixItems + items aufgeteilt, unterstützt $dynamicRef

Zwei Migrationsfallen:

  1. Tupelsyntax hat sich geändert: In draft-07 drückte ein arraywertiges items ein Tupel aus; 2020-12 verlagerte das zu prefixItems;
  2. $ref und Schwester-Schlüsselwörter: In draft-07 werden Schwester-Schlüsselwörter ignoriert, wenn $ref vorhanden ist; 2020-12 erlaubt ihre Koexistenz.

Unterstützt der Validator nur draft-07, wird 2020-12-Syntax trotzdem stillschweigend ignoriert – und der gefährlichste Fall ist „Validierung besteht“, obwohl überhaupt nichts validiert wurde.

Wie generiere ich TypeScript-Typen aus einem Schema?

Zwei Wege: Typen aus dem Schema ableiten oder das Schema aus Typen generieren.

Vom Schema zu Typen (das Backend definiert den Vertrag, das Frontend konsumiert ihn):

import type { FromSchema } from 'json-schema-to-ts';

const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'string' },
    age: { type: 'integer' },
  },
  required: ['id'],
  additionalProperties: false,
} as const;

type User = FromSchema<typeof userSchema>;
// { id: string; age?: number }

Der Schlüssel ist as const: Ohne es wird type: 'string' als string inferiert statt als Literal 'string', und die Inferenz bricht zusammen.

Von Typen zu Schema (das Frontend hat bereits Typen und braucht Laufzeitvalidierung):

import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

const userSchema = z.object({ id: z.string(), age: z.number().int().optional() });
const jsonSchema = zodToJsonSchema(userSchema);

Der Vorteil dieses Weges ist, dass „eine Definition sowohl Typen als auch Laufzeitvalidierung liefert“; der Preis ist die Einführung einer DSL, und das generierte Schema kann Implementierungsdetails tragen (etwa eine unschöne $ref-Struktur).

Wie lokalisiere ich das konkrete Feld, wenn die Validierung scheitert?

Sehen Sie sich den instancePath (2020-12) oder dataPath (draft-07) an, den der Validator zurückgibt; es ist ein JSON Pointer auf die Fehlerstelle.

[
  {
    "instancePath": "/items/2/price",
    "schemaPath": "#/properties/items/items/properties/price/type",
    "keyword": "type",
    "message": "must be number"
  }
]

instancePath: "/items/2/price" sagt Ihnen direkt: Das price-Feld des dritten Elements im items-Array hat den falschen Typ.

Zwei praktische Empfehlungen:

  1. Alle Fehler auf einmal zurückgeben, statt beim ersten zu werfen – das Frontend alles in einem Durchgang reparieren zu lassen ist schneller als Hin-und-Her-Versuche;
  2. Den Pointer in verständliche Sprache verwandeln, bevor Sie ihn Endnutzern zeigen; /items/2/price ist für Entwickler nützlich und für Nutzer wertlos.

allErrors: true ist der Schalter in den meisten Validatoren (Ajv verlangt ausdrückliches Einschalten); der Standard ist oft Fail-Fast-Modus.

Sollte additionalProperties: false aktiviert sein?

Aktivieren Sie es für interne Verträge; seien Sie bei öffentlichen APIs vorsichtig.

Einmal aktiviert, scheitert jedes Feld, das nicht unter properties deklariert ist, an der Validierung. Der Nutzen ist, Tippfehler (userNmae) und unerwarteten Durchfluss zu fangen; der Nachteil ist, dass eine Feldhinzufügung stromaufwärts stromabwärts Konsumenten bricht, was die Rückwärtskompatibilität zerstört.

Kompromiss:

Szenario Empfehlung
Zwischen internen Diensten Aktivieren. Feldänderungen laufen über Versionsmanagement
Öffentliche externe APIs Deaktivieren. Dem Upstream erlauben, Felder hinzuzufügen
Drittanbieter-Daten konsumieren Deaktivieren. Sie kontrollieren die andere Seite nicht
Konfigurationsvalidierung Aktivieren. Ein Tippfehler in der Konfiguration sollte sofort scheitern

Wie validiere ich, dass Array-Elemente eindeutig sind?

Zwei verschiedene Semantiken – verwechseln Sie sie nicht:

{ "uniqueItems": true }

Das dedupliziert das ganze Element und verlangt, dass das Array keine zwei tiefgleich Elemente enthält.

{
  "type": "array",
  "items": { "type": "object", "properties": { "id": { "type": "string" } } },
  "uniqueItems": true
}

Das obige Fragment garantiert nur, dass die Objekte als Ganzes nicht dupliziert sind; {"id":"a","name":"x"} und {"id":"a","name":"y"} zählen nicht als Duplikate.

Nach einem bestimmten Feld zu deduplizieren kann Schema nicht ausdrücken; Sie müssen in der Geschäftsschicht validieren:

const ids = items.map((item) => item.id);
if (new Set(ids).size !== ids.length) {
  throw new Error('id 重复');
}

Validiert format tatsächlich E-Mails und Daten?

Nicht unbedingt. In der Spezifikation ist format standardmäßig eine „Annotation“ und keine „Zusicherung“, ein Validator kann es also sammeln, ohne es zu validieren.

  • Ajv erfordert die Installation und Konfiguration des separaten ajv-formats-Pakets, sonst ist format: "email" praktisch wirkungslos;
  • Manche Validatoren behandeln format als standardmäßig aktivierte Zusicherung, das Verhalten ist also inkonsistent;
  • Selbst aktiviert ist das email-Regex sehr locker – a@b besteht.

Fazit: Behandeln Sie format nicht als starke Validierung. Validieren Sie E-Mails, URLs und Daten streng in Ihrem Geschäftscode; format im Schema als Dokumentation zu behalten ist lohnenswert, aber erwarten Sie nicht, dass es schmutzige Daten aufhält.

3. Kurzreferenz

Frage Schlussfolgerung
Knoten nach Bedingung finden JSONPath
Präzise Adressierung, Patches anwenden JSON Pointer
Array-Elemente ändern JSON Patch, Merge Patch kann es nicht
Konfiguration zusammenführen Merge Patch
Schema-Version für neue Projekte 2020-12, prefixItems beachten
TS-Typen aus Schema generieren json-schema-to-ts + as const
Validierungsfehler lokalisieren instancePath lesen, allErrors aktivieren
Nach Feld deduplizieren Schema kann es nicht; ein Set in der Geschäftsschicht verwenden
format: email Die meisten Validatoren prüfen es nicht standardmäßig, verlassen Sie sich nicht darauf