Wenn du schon einmal eine tief verschachtelte API-Antwort angestarrt und gedacht hast "ich brauche nur dieses eine Feld da drin" — JSONPath ist das Werkzeug, das du suchst. Es ist eine Abfragesprache für JSON, vom Geist her ähnlich wie XPath für XML. Du schreibst einen Pfadausdruck und bekommst die passenden Werte zurück. Keine Schleifen, keine manuelle Traversierung. Stefan Goessner hat es 2007 eingeführt, und nach Jahren von leicht inkompatiblen Implementierungen wurde es 2024 formell als RFC 9535 standardisiert. Du kannst JSONPath-Ausdrücke auch interaktiv mit dem JSON-Path-Tool ausprobieren.

Das Dataset, das wir durchgehend verwenden

Statt abstrakter Beispiele arbeiten wir mit etwas Realistischem — einer E-Commerce-Bestellantwort. Das ist die Art von JSON, die du von einer Bestellverwaltungs-API zurückbekommen würdest:

json
{
  "order": {
    "id": "ORD-9182",
    "status": "shipped",
    "customer": {
      "id": "CUST-441",
      "name": "Maria Chen",
      "email": "[email protected]"
    },
    "shippingAddress": {
      "street": "14 Maple Avenue",
      "city": "Portland",
      "state": "OR",
      "zip": "97201",
      "country": "US"
    },
    "lineItems": [
      {
        "sku": "WH-1042",
        "name": "Wireless Headphones",
        "qty": 1,
        "unitPrice": 89.99,
        "inStock": true
      },
      {
        "sku": "CB-USB-C",
        "name": "USB-C Charging Cable",
        "qty": 2,
        "unitPrice": 12.49,
        "inStock": true
      },
      {
        "sku": "SC-PRO-7",
        "name": "Phone Stand Pro",
        "qty": 1,
        "unitPrice": 34.00,
        "inStock": false
      }
    ],
    "totals": {
      "subtotal": 148.97,
      "shipping": 5.99,
      "tax": 11.92,
      "total": 166.88
    }
  }
}

Der Root-Operator $ und grundlegende Navigation

Jeder JSONPath-Ausdruck beginnt mit $, das auf die Wurzel des Dokuments verweist. Von dort aus navigierst du mit Punkten. Den Bestellstatus willst du? Das ist straightforward:

text
$.order.status
// → "shipped"

$.order.customer.name
// → "Maria Chen"

$.order.totals.total
// → 166.88

$.order.shippingAddress.city
// → "Portland"

Du kannst auch die Bracket-Notation verwenden, die nützlich ist, wenn ein Key Leerzeichen oder Sonderzeichen enthält, oder wenn du auf Array-Elemente per Index zugreifst. Beide Stile sind laut RFC 9535-Syntaxregeln gültig:

text
// Punkt-Notation
$.order.customer.email

// Bracket-Notation — äquivalent
$['order']['customer']['email']

// Array-Index — erstes Zeilenelement
$.order.lineItems[0].name
// → "Wireless Headphones"

// Letztes Element (negativer Index — in RFC 9535 unterstützt)
$.order.lineItems[-1].name
// → "Phone Stand Pro"
Hinweis zur negativen Indexierung: $[-1:] (Slice-Syntax) und $.array[-1] (direkter negativer Index) sind beide in RFC 9535 gültig, aber manche älteren Implementierungen unterstützen sie nicht. Teste gegen deine spezifische Bibliothek, wenn du negative Indizes verwendest.

Wildcards — Alle Kinder auf einmal abfragen

Der *-Wildcard passt auf alle Elemente einer bestimmten Ebene. Statt lineItems[0], lineItems[1] usw. abzufragen, kannst du alles auf einmal holen:

text
// Alle Zeilenelement-Namen
$.order.lineItems[*].name
// → ["Wireless Headphones", "USB-C Charging Cable", "Phone Stand Pro"]

// Alle Einzelpreise
$.order.lineItems[*].unitPrice
// → [89.99, 12.49, 34.00]

// Alle SKUs
$.order.lineItems[*].sku
// → ["WH-1042", "CB-USB-C", "SC-PRO-7"]

Das ist das Muster, das du am häufigsten verwendest, wenn du mit Arrays in API-Antworten arbeitest. Statt im Anwendungscode über ein Array zu mappen, kannst du genau das Feld extrahieren, das du brauchst, bevor es jemals in deine Business-Logik gelangt.

Rekursiver Abstieg — Werte in beliebiger Tiefe finden

Der ..-Operator führt eine rekursive Suche durch den gesamten Dokumentbaum durch. Es ist wie ein Tiefensuche-Durchlauf, der jeden Knoten mit dem passenden Key-Namen sammelt, egal wo er sitzt:

text
// Jedes "name"-Feld irgendwo im Dokument finden
$..name
// → ["Maria Chen", "Wireless Headphones", "USB-C Charging Cable", "Phone Stand Pro"]

// Jedes "city"-Feld überall finden
$..city
// → ["Portland"]

// Jedes Feld namens "id" in beliebiger Tiefe finden
$..id
// → ["ORD-9182", "CUST-441"]

Beachte, wie $..name sowohl den Kundennamen als auch alle Produktnamen findet — die Tiefe ist egal. Das ist mächtig für die Schema-Erkundung: Wenn du einen unbekannten JSON-Blob bekommst und alle Werte für einen bestimmten Key finden willst, ohne die Struktur zu kennen. Wenn du das JSON selbst zuerst prüfen willst, sind der JSON Validator und der JSON Formatter praktische Ausgangspunkte.

Array-Slicing

JSONPath übernimmt die Slice-Notation im Python-Stil für Arrays. Das Format ist [start:end:step], wobei jeder Teil weggelassen werden kann. Das ist in RFC 9535 § 2.3.5 dokumentiert:

text
// Erste zwei Zeilenelemente
$.order.lineItems[0:2]
// → [{ sku: "WH-1042", ... }, { sku: "CB-USB-C", ... }]

// Ab Index 1 bis zum Ende
$.order.lineItems[1:]
// → [{ sku: "CB-USB-C", ... }, { sku: "SC-PRO-7", ... }]

// Nur letztes Element (Slice-Syntax — weit verbreitet unterstützt)
$.order.lineItems[-1:]
// → [{ sku: "SC-PRO-7", ... }]

// Jedes zweite Element (Step 2)
$.order.lineItems[0::2]
// → [{ sku: "WH-1042", ... }, { sku: "SC-PRO-7", ... }]

Filterausdrücke — Nach Bedingung abfragen

Hier verdient sich JSONPath wirklich seinen Platz. Die Filter-Syntax [?(...)] erlaubt es dir, Array-Elemente nach ihren Feldwerten abzufragen. Das Symbol @ verweist auf das aktuelle getestete Element. Stefan Goessners ursprünglicher Artikel von 2007 führte diese Syntax ein, und RFC 9535 hat sie formalisiert:

text
// Zeilenelemente unter $50
$.order.lineItems[?(@.unitPrice < 50)]
// → [{ sku: "CB-USB-C", unitPrice: 12.49, ... }, { sku: "SC-PRO-7", unitPrice: 34.00, ... }]

// Nur aktuell vorrätige Artikel
$.order.lineItems[?(@.inStock == true)]
// → [{ sku: "WH-1042", ... }, { sku: "CB-USB-C", ... }]

// Artikel, bei denen qty größer als 1 ist
$.order.lineItems[?(@.qty > 1)]
// → [{ sku: "CB-USB-C", qty: 2, ... }]

// Artikel, die NICHT vorrätig sind
$.order.lineItems[?(@.inStock == false)].name
// → ["Phone Stand Pro"]

Du kannst Bedingungen auch kombinieren. Die meisten Implementierungen unterstützen && und || innerhalb von Filterausdrücken, sodass du Dinge wie [?(@.inStock == true && @.unitPrice < 30)] schreiben kannst. Prüfe die Dokumentation deiner Bibliothek — das Verhalten rund um Operator-Unterstützung war eine der Hauptinkonsistenzen zwischen Vor-RFC-Implementierungen, die die RFC 9535-Standardisierung zu beheben versuchte.

Auf Feldexistenz prüfen: Um Elemente zu filtern, die ein bestimmtes Feld haben (unabhängig von seinem Wert), verwende die Existenzprüf-Syntax: [?(@.fieldName)]. Zum Beispiel, wenn einige Zeilenelemente ein discountCode-Feld hätten und andere nicht, würde $.order.lineItems[?(@.discountCode)] nur die rabattierten zurückgeben.

JSONPath in JavaScript verwenden

In JavaScript gibt es noch keine native JSONPath-Unterstützung (anders als XPath, das document.evaluate() im Browser hat). Du brauchst eine Bibliothek. Die zwei beliebtesten sind jsonpath und jsonpath-plus. Das Paket jsonpath-plus wird aktiver gewartet und hat bessere RFC 9535-Ausrichtung:

bash
npm install jsonpath-plus
js
import { JSONPath } from 'jsonpath-plus';

const order = {
  order: {
    id: 'ORD-9182',
    status: 'shipped',
    customer: { name: 'Maria Chen', email: '[email protected]' },
    lineItems: [
      { sku: 'WH-1042', name: 'Wireless Headphones', qty: 1, unitPrice: 89.99, inStock: true },
      { sku: 'CB-USB-C', name: 'USB-C Charging Cable', qty: 2, unitPrice: 12.49, inStock: true },
      { sku: 'SC-PRO-7', name: 'Phone Stand Pro', qty: 1, unitPrice: 34.00, inStock: false }
    ],
    totals: { subtotal: 148.97, shipping: 5.99, tax: 11.92, total: 166.88 }
  }
};

// Alle Produktnamen holen
const names = JSONPath({ path: '$.order.lineItems[*].name', json: order });
console.log(names);
// [ 'Wireless Headphones', 'USB-C Charging Cable', 'Phone Stand Pro' ]

// Vorrätige Artikel unter $50 holen
const affordable = JSONPath({ path: '$.order.lineItems[?(@.inStock == true && @.unitPrice < 50)]', json: order });
console.log(affordable.map(item => item.name));
// [ 'USB-C Charging Cable' ]

// Den Bestellgesamtbetrag holen
const total = JSONPath({ path: '$.order.totals.total', json: order });
console.log(total[0]); // 166.88

Beachte, dass JSONPath() immer ein Array zurückgibt — auch wenn du einen einzelnen Wert abfragst. Das ist by Design: Ein Pfadausdruck ist ein Selektor, und ein Selektor kann null, einen oder viele Knoten treffen. Also greife immer mit result[0] zu, wenn du weißt, dass du auf einen eindeutigen Wert abzielst.

JSONPath in Python verwenden

In Python ist die jsonpath-ng-Bibliothek die vollständigste Option. Sie unterstützt den Kern-Spec plus den Großteil der Filterausdruck-Syntax:

bash
pip install jsonpath-ng
python
from jsonpath_ng import parse

order = {
    "order": {
        "id": "ORD-9182",
        "status": "shipped",
        "customer": {"name": "Maria Chen", "email": "[email protected]"},
        "lineItems": [
            {"sku": "WH-1042", "name": "Wireless Headphones", "qty": 1, "unitPrice": 89.99, "inStock": True},
            {"sku": "CB-USB-C", "name": "USB-C Charging Cable", "qty": 2, "unitPrice": 12.49, "inStock": True},
            {"sku": "SC-PRO-7", "name": "Phone Stand Pro", "qty": 1, "unitPrice": 34.00, "inStock": False}
        ],
        "totals": {"subtotal": 148.97, "shipping": 5.99, "tax": 11.92, "total": 166.88}
    }
}

# Ausdruck einmal parsen, dann wiederverwenden (effizienter)
expr = parse("$.order.lineItems[*].name")
names = [match.value for match in expr.find(order)]
print(names)
# ['Wireless Headphones', 'USB-C Charging Cable', 'Phone Stand Pro']

# Alle Zeilenelemente holen und Lagerbestand prüfen
expr2 = parse("$.order.lineItems[*]")
for item in [m.value for m in expr2.find(order)]:
    status = "in stock" if item["inStock"] else "OUT OF STOCK"
    print(f"{item['name']} (${item['unitPrice']}) — {status}")

# Ausgabe:
# Wireless Headphones ($89.99) — in stock
# USB-C Charging Cable ($12.49) — in stock
# Phone Stand Pro ($34.0) — OUT OF STOCK

Das Muster parse() + find() ist der richtige Ansatz für den Produktionseinsatz — kompiliere den Ausdruck einmal und führe ihn gegen viele Dokumente aus, anstatt den Pfad-String jedes Mal neu zu parsen. Die jsonpath-ng-Dokumentation auf GitHub enthält mehr Details zur erweiterten Filtersyntax und zur update()-Methode zum Ändern übereinstimmender Knoten.

JSONPath vs jq — Was sollte ich verwenden?

Manchmal werden JSONPath und jq durcheinandergebracht — beide "fragen JSON ab", aber sie lösen unterschiedliche Probleme. Hier ist die praktische Gegenüberstellung:

  • JSONPath ist eine Pfadausdruckssprache, die dafür entwickelt wurde, in Anwendungen eingebettet zu werden. Sie fragt Werte ab und extrahiert sie. Du verwendest es in JavaScript-, Python-, Java-Code oder Konfigurationsdateien wie Kubernetes-Selektoren.
  • jq ist ein vollständiger Kommandozeilenprozessor mit eigener Programmiersprache. Er kann abfragen, transformieren, umformen, filtern und neue Werte berechnen. Es ist das richtige Werkzeug für Shell-Skripte und einmalige Datenwrangling im Terminal.
  • JSONPath verwenden, wenn du Anwendungscode schreibst, der programmatisch Felder aus JSON extrahieren muss — besonders wenn die Bibliothek einbettbar und leichtgewichtig sein muss.
  • jq verwenden, wenn du an einer Kommandozeile bist, Shell-Skripte schreibst, API-Antworten debuggst oder Transformationen vornehmen musst (Felder umbenennen, aggregieren, neue Strukturen aufbauen), die über einfache Extraktion hinausgehen.
  • jq-Syntax ist mächtiger, aber auch komplexer — jq '.order.lineItems[] | select(.inStock) | .name' vs JSONPaths $.order.lineItems[?(@.inStock)].name. Für reine Extraktion ist JSONPath oft lesbarer.

Es gibt auch eine Mitte: Wenn du bereits in JavaScript arbeitest und nur gelegentlich Transformationen brauchst, deckt etwas wie Lodashs _.get() einfachen Pfadzugriff ohne Abhängigkeiten ab. Aber für alles mit Wildcards, Rekursion oder Filterausdrücken lohnt sich eine ordentliche JSONPath-Bibliothek.

Zusammenfassung

JSONPath füllt eine echte Lücke zwischen "dieses Objekt manuell in einer for-Schleife traversieren" und "einen jq-Prozess starten". Sobald du dich mit $, ., [*], .. und [?(filter)] wohlfühlst, wirst du ständig danach greifen — besonders bei großen API-Payloads, wo du nur eine Handvoll Felder brauchst. Die RFC 9535-Standardisierung bedeutet, dass die Syntax jetzt stabil ist, sodass Ausdrücke, die du heute schreibst, konsistent über konforme Bibliotheken hinweg funktionieren sollten. Teste deine Ausdrücke mit dem JSON-Path-Tool — füge dein JSON ein, schreibe einen Pfad und sieh die Ergebnisse sofort. Und wenn dein JSON erst bereinigt werden muss, sind der JSON Formatter und der JSON Validator gleich nebenan.