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.

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→1und3, ohne das verschachtelte2.
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:
- Tupelsyntax hat sich geändert: In draft-07 drückte ein arraywertiges
itemsein Tupel aus; 2020-12 verlagerte das zuprefixItems; $refund Schwester-Schlüsselwörter: In draft-07 werden Schwester-Schlüsselwörter ignoriert, wenn$refvorhanden 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:
- 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;
- Den Pointer in verständliche Sprache verwandeln, bevor Sie ihn Endnutzern zeigen;
/items/2/priceist 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 istformat: "email"praktisch wirkungslos; - Manche Validatoren behandeln
formatals standardmäßig aktivierte Zusicherung, das Verhalten ist also inkonsistent; - Selbst aktiviert ist das
email-Regex sehr locker –a@bbesteht.
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 |