JSON Toolkit

Die JSONPath-Abfragesprache in der Praxis

Fazit: 2024 bekam JSONPath endlich einen formalen Standard, RFC 9535. Dieser Artikel erklärt Syntax, Filtersemantik, Funktionen und die historischen Unterschiede zwischen Implementierungen.

Fortgeschritten~12 Min. LesezeitVeröffentlicht 2026-10-30Aktualisiert 2026-10-30
Titelbild für Die JSONPath-Abfragesprache in der Praxis

Kurz gesagt: Lokalisieren Sie zuerst mit einem ausdrücklichen Pfad und filtern Sie darauf aufbauend, und schreiben Sie Ausdrücke gegen RFC 9535; für ein einzelnes festes Feld Property-Zugriff oder JSON Pointer verwenden, und für Aggregation jq oder Code.

Sie haben wahrscheinlich Ausdrücke wie $.store.book[?(@.price < 10)].title gesehen, aber erst 2024 wurde daraus ein internationaler Standard.

1. Vom De-facto-Standard zum formalen Standard

JSONPath wurde 2007 von Stefan Gössner vorgeschlagen, und lange war die einzige „Spezifikation“ ein einzelner Blogbeitrag, während jede Sprachimplementierung ihr eigenes Ding machte:

  • ob Filter Klammern brauchen (?(@.a) vs. ?@.a);
  • ob der Array-Index [-1] unterstützt wird;
  • ob das Ergebnis „Werte“ oder „Werte und Pfade“ liefert;
  • ob Skriptausdrücke (?(@.price < $.max)) erlaubt sind.

RFC 9535, im Februar 2024 veröffentlicht, führte diese Abweichungen zu einer einzigen Syntax und Semantik zusammen und stellte eine offizielle Konformitätstestsuite bereit. Neue Projekte sollten RFC 9535 folgen; bei der Migration alter Projekte zuerst die Implementierungsversion bestätigen.

2. Syntax-Spickzettel

Syntax Bedeutung Beispiel
$ Wurzelknoten $.store
@ Aktueller Knoten innerhalb eines Filters $[?@.price < 10]
.name Kindknoten (der Name muss [A-Za-z_][A-Za-z0-9_]* entsprechen) $.store.book
['name'] Kindknoten (beliebiger Name, auch nicht-ASCII und Punkte) $['store']['书']
[0] Array-Index; negative Zahlen zählen von hinten $.book[0], $.book[-1]
[0,2] Mehrere Indizes / mehrere Namen $.book[0,2]
[1:3] Slice, inklusiver Anfang und exklusives Ende $.book[1:3]
[::2] Slice mit Schrittweite $.book[::2]
[*] Platzhalter für alle Kindknoten $.book[*].title
..name Rekursiver Abstieg, Suche in beliebiger Tiefe $..price
..[*] Rekursiv alle Knoten nehmen $..*
[?<expr>] Filterselektor $.book[?@.price < 10]

Eine Semantik müssen Sie sich merken: JSONPath gibt eine Knotenmenge (Nodelist) zurück, keinen einzelnen Wert. Selbst wenn nur ein Knoten passt, ist das Ergebnis eine Liste der Länge eins.

3. Die Syntax mit einem Beispiel durchspielen

{
  "store": {
    "book": [
      { "title": "JSON 权威指南", "price": 8.95, "tags": ["json", "spec"] },
      { "title": "数据交换格式史", "price": 12.99, "tags": ["history"] },
      { "title": "解析器实现原理", "price": 8.99, "tags": ["parser", "json"] }
    ],
    "bicycle": { "color": "red", "price": 19.95 }
  },
  "expensive": 10
}
Ausdruck Ergebnis
$.store.bicycle.color ["red"]
$..price 4 Preise (einschließlich des Fahrradpreises)
$.store.book[*].title 3 Titel
$.store.book[?@.price < 10].title Titel der Bücher 1 und 3
$.store.book[?@.tags] Alle Bücher mit tags (Existenztest)
$.store.book[-1].title Titel des letzten Buchs
$..book[0,1] Die ersten beiden Bücher
$.store.book[?@.price < $.expensive] Syntaktisch implementierungsabhängig; RFC 9535 erlaubt keine Referenzen über die Wurzel

Die letzte Zeile ist eine historische Falle: Viele Implementierungen erlauben $.expensive innerhalb eines Filters für den Vergleich über Knoten hinweg, was RFC 9535 ausdrücklich verbietet – ein Filter kann nur auf den aktuellen Knoten @ zugreifen. Müssen Sie gegen einen globalen Schwellenwert vergleichen, übergeben Sie den Schwellenwert als Argument an die Abfragefunktion.

4. Was Filter ausdrücken können

RFC-9535-Filterausdrücke unterstützen vier Arten von Konstrukten:

Logic:       &&  ||  !          e.g. ?@.price < 10 && @.tags
Comparison:  ==  !=  <  <=  >  >=
Existence:   a bare path        e.g. ?@.tags (tests existence)
Functions:   length() count() match() search() value()

Einige praktische Beispiele:

// Price between 8 and 10
$.store.book[?@.price >= 8 && @.price <= 10]

// Title contains "JSON" (regex match; the second argument is an I-Regexp)
$.store.book[?match(@.title, ".*JSON.*")]

// More than one tag
$.store.book[?length(@.tags) > 1]

// Get the first matched value instead of a list (value() turns a single-node set into a value)
$.store.book[?@.price < 9].title.value()

match() ist ein Ganzstring-Match und search() eine Teilstring-Suche; der zugrunde liegende Regex-Dialekt ist I-Regexp (eine Teilmenge von RFC 9485), der weder Backreferences noch Lookaround unterstützt – das ist sowohl eine Einschränkung als auch ein praktischer Schutz gegen Regex-DoS.

5. Implementierungsunterschiede: vor der Migration prüfen

Implementierung Sprache RFC-9535-Unterstützung Hinweise
jsonpath-plus JavaScript Weitgehend kompatibel Unterstützt Erweiterungsfunktionen und Skriptausdrücke; fähiger, aber nicht streng standardkonform
jsonpath (dchester) JavaScript Teilweise Lang bestehende Bibliothek mit ?()-Syntax und anderer Ergebnisstruktur
ojg / gjson Go ojg hat gute Unterstützung gjson verwendet eine andere Pfadsyntax und ist kein JSONPath
jsonpath-ng Python Teilweise Unterstützt Erweiterungen und ist recht nachsichtig
Jayway JsonPath Java Teilweise Breites Ökosystem; historisches Verhalten weicht vom RFC ab

Drei Prüfungen vor der Migration:

  1. Ob Filterklammern erforderlich sind;
  2. Ob negative Indizes wie -1 unterstützt werden;
  3. Ob das Ergebnis ein „Array von Werten“ oder ein „Array von {path, value}-Objekten“ ist.

Das dritte ist am leichtesten zu übersehen: Das Frontend liest result[0], bekommt nach dem Bibliothekswechsel ein Objekt und lässt die Seite leer.

6. Performance und Sicherheit

Performance

  • JSONPath ist im Wesentlichen ein einziger Durchlauf mit O(Knotenzahl) Komplexität und kann keine Indizes nutzen;
  • Rekursiver Abstieg mit .. ist am teuersten; können Sie zuerst mit $.store.book lokalisieren und dann filtern, schreiben Sie nicht $..book;
  • Ausdrücke einmal kompilieren und wiederverwenden (die meisten Bibliotheken bieten compile oder Caching), statt bei jeder Abfrage neu zu parsen;
  • Bei Dokumenten mit mehr als 100.000 Knoten erwägen Sie zuerst exakte Extraktion mit JSON Pointer oder Vorverarbeitung auf dem Server mit jq.

Sicherheit

  • Ausdrücke aus Benutzereingaben müssen Grenzen für Länge und Verschachtelungstiefe haben;
  • Lassen Sie JSONPath keine Autorisierung übernehmen – es kann nur lesen und nicht ausdrücken, „welche Felder unsichtbar sind“;
  • Kommt der von match() verwendete Regex von Nutzern, begrenzen Sie seine Komplexität; obwohl er sicherer als PCRE ist, ist eine Längenobergrenze dennoch empfehlenswert.

7. Wann JSONPath nicht verwendet werden sollte

  • Sie brauchen nur ein festes Feld → data.store.bicycle.color direkt verwenden;
  • Sie müssen nach dem Ändern zurückschreiben → JSON Pointer oder einen Patch verwenden; JSONPath schreibt nicht zurück;
  • Sie brauchen Aggregation (Summieren, Gruppieren) → jq, SQL oder Code verwenden; JSONPath wählt nur aus;
  • Das Dokument ist riesig und Sie brauchen nur wenige Felder → einen Streaming-Parser verwenden, statt den ganzen Baum zuerst in den Speicher zu lesen.

8. Checkliste

  • [ ] Ausdrücke folgen RFC 9535 und verlassen sich nicht auf Referenzen über die Wurzel;
  • [ ] Es ist klar, dass das Ergebnis eine Knotenmenge ist; [0] oder value() verwenden, um einen einzelnen Wert zu erhalten;
  • [ ] Ausdrücke werden vorkompiliert und wiederverwendet;
  • [ ] Von Nutzern kontrollierte Ausdrücke haben Längen- und Komplexitätsgrenzen;
  • [ ] Bei einem Wechsel der Implementierung werden die offiziellen Konformitätstestfälle ausgeführt.

FAQ

Wie wähle ich zwischen JSON Pointer und JSONPath?

Verwenden Sie einfachen Property-Zugriff oder einen JSON Pointer, wenn Sie nur ein festes Feld brauchen; greifen Sie zu JSONPath, wenn Sie Filter, Platzhalter oder rekursiven Abstieg brauchen. JSONPath wählt nur aus und schreibt nie zurück, Zurückschreiben gehört also zu JSON Pointer oder einem Patch.

Warum kann ein Filter nicht auf $.expensive verweisen?

RFC 9535 verbietet ausdrücklich Referenzen über die Wurzel; ein Filter darf nur auf den aktuellen Knoten @ zugreifen. Übergeben Sie den Schwellenwert stattdessen als Argument an die Abfragefunktion. Ältere Implementierungen akzeptieren die Syntax, aber sie bricht beim Wechsel oder Upgrade von Bibliotheken.

Sollte ich mich auf result[0] verlassen, um einen Wert zu lesen?

Nur nachdem Sie bestätigt haben, dass die aktuelle Implementierung ein Array von Werten zurückgibt. Manche Bibliotheken geben {path, value}-Objekte zurück, dann wird result[0] zu einem Objekt und die Seite bleibt leer. Lesen Sie einzelne Werte über [0] oder value(), und führen Sie vor einem Wechsel der Implementierung die offizielle Konformitätssuite aus.