Jeśli kiedykolwiek wpatrywałeś się w głęboko zagnieżdżoną odpowiedź API i myślałeś „potrzebuję tylko tego jednego pola ukrytego gdzieś w środku" — JSONPath to narzędzie, którego szukasz. To język zapytań dla JSON, podobny duchem do tego, jak XPath działa dla XML. Piszesz wyrażenie ścieżki, a w zamian dostajesz pasujące wartości. Żadnych pętli, żadnego ręcznego przechodzenia. Stefan Goessner wprowadził go w 2007 roku, a po latach nieznacznie niekompatybilnych implementacji krążących w sieci, został formalnie ustandaryzowany jako RFC 9535 w 2024 roku. Wyrażenia JSONPath możesz też eksplorować interaktywnie za pomocą narzędzia JSON Path.
Zbiór danych, którego będziemy używać
Zamiast abstrakcyjnych przykładów, pracujmy z czymś realistycznym — odpowiedzią e-commerce z zamówieniem. To rodzaj JSON, który otrzymasz z API zarządzania zamówieniami:
{
"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
}
}
}Operator root $ i podstawowa nawigacja
Każde wyrażenie JSONPath zaczyna się od $, które odnosi się do korzenia dokumentu.
Stamtąd poruszasz się za pomocą kropek. Chcesz poznać status zamówienia? To proste:
$.order.status
// → "shipped"
$.order.customer.name
// → "Maria Chen"
$.order.totals.total
// → 166.88
$.order.shippingAddress.city
// → "Portland"Możesz też używać notacji nawiasowej, która jest przydatna, gdy klucz zawiera spacje lub znaki specjalne, albo gdy odwołujesz się do elementów tablicy po indeksie. Oba style są zgodne z zasadami składni RFC 9535:
// Notacja kropkowa
$.order.customer.email
// Notacja nawiasowa — równoważna
$['order']['customer']['email']
// Indeks tablicy — pierwszy element zamówienia
$.order.lineItems[0].name
// → "Wireless Headphones"
// Ostatni element zamówienia (ujemne indeksowanie — obsługiwane w RFC 9535)
$.order.lineItems[-1].name
// → "Phone Stand Pro"$[-1:] (składnia slice) i
$.array[-1] (bezpośredni ujemny indeks) są oba poprawne w RFC 9535, ale niektóre starsze
implementacje ich nie obsługują. Przetestuj swoją konkretną bibliotekę, jeśli używasz ujemnych indeksów.Wieloznaczniki — jednoczesne zapytanie do wszystkich dzieci
Wieloznacznik * dopasowuje wszystkie elementy na danym poziomie. Zamiast pytać o
lineItems[0], lineItems[1] itd., możesz pobrać wszystko naraz:
// Wszystkie nazwy pozycji zamówienia
$.order.lineItems[*].name
// → ["Wireless Headphones", "USB-C Charging Cable", "Phone Stand Pro"]
// Wszystkie ceny jednostkowe pozycji zamówienia
$.order.lineItems[*].unitPrice
// → [89.99, 12.49, 34.00]
// Wszystkie kody SKU pozycji zamówienia
$.order.lineItems[*].sku
// → ["WH-1042", "CB-USB-C", "SC-PRO-7"]To wzorzec, który będziesz najczęściej stosować przy pracy z tablicami w odpowiedziach API. Zamiast mapować po tablicy w kodzie aplikacji, możesz wyodrębnić dokładnie potrzebne pole zanim dotrze ono do logiki biznesowej.
Rekurencyjne przeszukiwanie — znajdowanie wartości na każdej głębokości
Operator .. przeprowadza rekurencyjne przeszukiwanie przez cały drzewo dokumentu.
To jak przejście w głąb, które zbiera każdy węzeł pasujący do nazwy klucza, niezależnie od tego, gdzie się znajduje:
// Znajdź każde pole "name" gdziekolwiek w dokumencie
$..name
// → ["Maria Chen", "Wireless Headphones", "USB-C Charging Cable", "Phone Stand Pro"]
// Znajdź każde pole "city" gdziekolwiek
$..city
// → ["Portland"]
// Znajdź każde pole o nazwie "id" na dowolnej głębokości
$..id
// → ["ORD-9182", "CUST-441"]Zauważ, jak $..name znajduje zarówno imię klienta, jak i wszystkie nazwy produktów —
nie obchodzi go głębokość. Jest to potężne narzędzie do eksploracji schematu: gdy masz do czynienia z
nieznanym obiektem JSON i chcesz znaleźć wszystkie wartości dla konkretnego klucza bez znajomości struktury.
Jeśli chcesz najpierw sprawdzić sam JSON, JSON Validator
i JSON Formatter są przydatnymi punktami startowymi.
Wycinanie tablic
JSONPath zapożycza notację wycinków w stylu Pythona dla tablic. Format to
[start:end:step], gdzie każda część może być pominięta.
Jest to udokumentowane w RFC 9535 § 2.3.5:
// Pierwsze dwie pozycje zamówienia
$.order.lineItems[0:2]
// → [{ sku: "WH-1042", ... }, { sku: "CB-USB-C", ... }]
// Od indeksu 1 do końca
$.order.lineItems[1:]
// → [{ sku: "CB-USB-C", ... }, { sku: "SC-PRO-7", ... }]
// Tylko ostatni element (składnia slice — szeroko obsługiwana)
$.order.lineItems[-1:]
// → [{ sku: "SC-PRO-7", ... }]
// Co drugi element (krok 2)
$.order.lineItems[0::2]
// → [{ sku: "WH-1042", ... }, { sku: "SC-PRO-7", ... }]Wyrażenia filtrujące — zapytania według warunku
Tu JSONPath naprawdę pokazuje swoją wartość. Składnia filtra [?(...)]
pozwala na zapytania o elementy tablicy według wartości ich pól. Symbol @ odnosi się do aktualnie
testowanego elementu. Oryginalny artykuł Stefana Goessnera z 2007 roku na
stronie goessner.net
wprowadził tę składnię, a RFC 9535 ją sformalizował:
// Pozycje poniżej 50 USD
$.order.lineItems[?(@.unitPrice < 50)]
// → [{ sku: "CB-USB-C", unitPrice: 12.49, ... }, { sku: "SC-PRO-7", unitPrice: 34.00, ... }]
// Tylko produkty aktualnie dostępne w magazynie
$.order.lineItems[?(@.inStock == true)]
// → [{ sku: "WH-1042", ... }, { sku: "CB-USB-C", ... }]
// Pozycje, gdzie qty jest większe niż 1
$.order.lineItems[?(@.qty > 1)]
// → [{ sku: "CB-USB-C", qty: 2, ... }]
// Pozycje niedostępne w magazynie
$.order.lineItems[?(@.inStock == false)].name
// → ["Phone Stand Pro"]Możesz też łączyć warunki. Większość implementacji obsługuje && i ||
wewnątrz wyrażeń filtrujących, co pozwala pisać rzeczy takie jak
[?(@.inStock == true && @.unitPrice < 30)].
Sprawdź dokumentację swojej biblioteki — różnice w obsłudze operatorów były jedną z głównych niespójności
między implementacjami sprzed RFC, które
standaryzacja RFC 9535 miała naprawić.
[?(@.fieldName)]. Na przykład,
jeśli niektóre pozycje miały pole discountCode, a inne nie,
$.order.lineItems[?(@.discountCode)] zwróci tylko te ze zniżką.Używanie JSONPath w JavaScript
JavaScript nie ma jeszcze wbudowanej obsługi JSONPath (w przeciwieństwie do XPath, który ma
document.evaluate() w przeglądarce). Będziesz potrzebować biblioteki.
Dwie najpopularniejsze to jsonpath
i jsonpath-plus.
Pakiet jsonpath-plus jest aktywniej utrzymywany i lepiej zgodny z RFC 9535:
npm install jsonpath-plusimport { 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 }
}
};
// Get all product names
const names = JSONPath({ path: '$.order.lineItems[*].name', json: order });
console.log(names);
// [ 'Wireless Headphones', 'USB-C Charging Cable', 'Phone Stand Pro' ]
// Get in-stock items under $50
const affordable = JSONPath({ path: '$.order.lineItems[?(@.inStock == true && @.unitPrice < 50)]', json: order });
console.log(affordable.map(item => item.name));
// [ 'USB-C Charging Cable' ]
// Get the order total
const total = JSONPath({ path: '$.order.totals.total', json: order });
console.log(total[0]); // 166.88Zwróć uwagę, że JSONPath() zawsze zwraca tablicę — nawet gdy zapytujesz o pojedynczą wartość.
To zamierzone: wyrażenie ścieżki to selektor, a selektor może dopasować zero, jeden lub wiele węzłów.
Dlatego zawsze sięgaj po result[0], gdy wiesz, że celujesz w unikalną wartość.
Używanie JSONPath w Pythonie
W Pythonie biblioteka
jsonpath-ng
jest najbardziej kompletną opcją. Obsługuje podstawową specyfikację plus większość składni wyrażeń filtrujących:
pip install jsonpath-ngfrom 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}
}
}
# Parse the expression once, then reuse it (more efficient)
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']
# Get all line items and check stock
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}")
# Output:
# Wireless Headphones ($89.99) — in stock
# USB-C Charging Cable ($12.49) — in stock
# Phone Stand Pro ($34.0) — OUT OF STOCKWzorzec parse() + find() to właściwe podejście w zastosowaniach produkcyjnych —
skompiluj wyrażenie raz i uruchamiaj je na wielu dokumentach, zamiast parsować ciąg ścieżki za każdym razem.
Dokumentacja jsonpath-ng na GitHub
zawiera więcej szczegółów na temat rozszerzonej składni filtrów i metody update() do modyfikowania dopasowanych węzłów.
JSONPath vs jq — po które narzędzie sięgnąć?
Ludzie czasem mylą JSONPath i jq — oba „odpytują JSON", ale rozwiązują różne problemy. Oto praktyczne porównanie:
- JSONPath to język wyrażeń ścieżek zaprojektowany do osadzania w aplikacjach. Odpytuje i wyodrębnia wartości. Używasz go wewnątrz kodu JavaScript, Python, Java lub plików konfiguracyjnych, takich jak selektory Kubernetes.
- jq to pełny procesor wiersza poleceń z własnym językiem programowania. Może odpytywać, transformować, przekształcać, filtrować i obliczać nowe wartości. To odpowiednie narzędzie do skryptów powłoki i jednorazowego przetwarzania danych w terminalu.
- Używaj JSONPath, gdy piszesz kod aplikacji, który musi programowo wyodrębniać pola z JSON — szczególnie gdy biblioteka musi być możliwa do osadzenia i lekka.
- Używaj jq, gdy pracujesz w wierszu poleceń, piszesz skrypty powłoki, debugujesz odpowiedzi API lub potrzebujesz transformacji (zmiana nazw pól, agregowanie, budowanie nowych struktur) wykraczających poza proste wyodrębnianie.
- Składnia jq jest bardziej rozbudowana, ale też bardziej złożona —
jq '.order.lineItems[] | select(.inStock) | .name'vs JSONPath$.order.lineItems[?(@.inStock)].name. W przypadku czystego wyodrębniania JSONPath jest często bardziej czytelny.
Istnieje też złoty środek: jeśli pracujesz już w JavaScript i potrzebujesz tylko okazjonalnych
transformacji, coś w stylu _.get() z Lodash
obsługuje prosty dostęp do ścieżek bez żadnych zależności. Ale do czegokolwiek obejmującego wieloznaczniki, rekurencję
lub wyrażenia filtrujące, właściwa biblioteka JSONPath jest warta zachodu.
Podsumowanie
JSONPath wypełnia prawdziwą lukę między „ręcznym przechodzeniem przez ten obiekt w pętli for" a
„uruchamianiem procesu jq". Gdy opanujesz $, ., [*],
.. i [?(filter)], będziesz po nie sięgać nieustannie —
szczególnie przy pracy z dużymi ładunkami API, gdzie potrzebujesz tylko kilku pól.
Standaryzacja RFC 9535
oznacza, że składnia jest teraz stabilna, więc wyrażenia napisane dziś powinny działać spójnie w
zgodnych bibliotekach.
Wypróbuj swoje wyrażenia za pomocą narzędzia JSON Path — wklej swój JSON,
napisz ścieżkę i natychmiast zobacz wyniki. A jeśli Twój JSON wymaga najpierw porządków,
JSON Formatter i JSON Validator
są zawsze pod ręką.