Skip to content
Powrót do bloga
Poradniki

Składnia JSONPath: zapytania i filtrowanie JSON z przykładami

Składnia JSONPath na przykładach: korzeń, zejście rekurencyjne, symbole wieloznaczne, wycinki i filtry. Testuj każde zapytanie w przeglądarce.

11 min czytania

Przewodnik po składni JSONPath: zapytania i filtrowanie JSON z przykładami

JSONPath to język zapytań dla JSON — dokładnie tak, jak XPath jest językiem zapytań dla XML. Piszesz wyrażenie ścieżki, a ewaluator zwraca każdą pasującą wartość. Żeby wyciągnąć wszystkie nazwiska autorów z dokumentu księgarni, wystarczy napisać $.store.book[*].author — i otrzymujesz listę autorów, bez ani jednej linijki kodu przemierzającego strukturę.

Ten przewodnik prowadzi przez każdy selektor składni JSONPath, z przykładami do skopiowania, które możesz uruchamiać podczas czytania. Jedną rzecz warto ustalić od razu: istnieją dwa dialekty. Dialekt Goessnera z 2007 roku to klasyk de facto, a RFC 9535 to formalny standard IETF opublikowany w lutym 2024 roku. Zgadzają się co do typowych ścieżek, a rozchodzą w przypadkach brzegowych, dlatego ten przewodnik zaznacza różnice na bieżąco. Każde wyrażenie poniżej możesz wypróbować w narzędziu JSONPath Tester i przełączać się między oboma silnikami, żeby je porównać.

Na początek ściąga z selektorami. Reszta artykułu rozwija każdy wiersz na działającym przykładzie opartym o jeden wspólny dokument JSON.

SelektorZnaczeniePrzykład
$Korzeń dokumentu$
@Bieżący element (w filtrach)[?(@.price < 10)]
.name / ['name']Składnik potomny$.store.book
..Zejście rekurencyjne$..author
*Wszystkie elementy / składniki$.store.book[*]
[0]Indeks tablicy$.store.book[0]
[start:end:step]Wycinek tablicy (półotwarty)$.store.book[0:2]
[a,b]Suma nazw / indeksów$.store.book[0,2]
[?()]Wyrażenie filtrujące$.store.book[?(@.price < 10)]
length() count() match() search() value()Funkcje RFC 9535 (tylko w filtrach)[?length(@.title) > 15]

Czym jest JSONPath?

JSONPath to deklaratywny język zapytań do wybierania węzłów z dokumentu JSON. Zamiast pisać pętlę, która przechodzi przez obiekty i tablice, opisujesz ścieżką lokalizację, której szukasz, a ewaluator zwraca pasujące wartości. Model myślowy jest taki sam, jaki XPath daje dla XML: ścieżka złożona z selektorów, które krok po kroku przechodzą przez strukturę.

JSONPath pojawia się wszędzie tam, gdzie programiści dotykają JSON. Używasz go do wyciągnięcia pola z odpowiedzi API, do asercji na wartości w teście integracyjnym, do adresowania pól w konfiguracjach potoków dla Kubernetes, AWS Step Functions i Azure Logic Apps oraz do wydobywania danych z dużego lub nieregularnego JSON bez ręcznego pisania logiki przechodzenia.

Krótka uwaga historyczna, bo wyjaśnia podział na dialekty. Stefan Goessner zaproponował JSONPath w 2007 roku. Szybko się rozpowszechnił i stał się standardem de facto, ale nigdy nie został formalnie wyspecyfikowany — więc implementacje rozeszły się w szczegółach. IETF zamknął tę lukę w lutym 2024 roku, publikując RFC 9535, pierwszą formalną specyfikację JSONPath. Oba dialekty żyją do dziś i właśnie dlatego to samo wyrażenie może zachowywać się różnie w zależności od tego, która biblioteka je uruchamia.

Zanim zaczniesz odpytywać, warto odczytać strukturę. Sformatuj nieczytelne dane narzędziem JSON Formatter, żeby zagnieżdżenie stało się widoczne.

Przykładowy dokument

Każdy przykład poniżej działa na klasycznym JSON-ie księgarni Goessnera. Wklej go raz i korzystaj z niego wielokrotnie:

{
  "store": {
    "book": [
      { "title": "Sayings of the Century", "author": "Nigel Rees", "price": 8.95 },
      { "title": "Sword of Honour", "author": "Evelyn Waugh", "price": 12.99 },
      { "title": "Moby Dick", "author": "Herman Melville", "price": 8.99 },
      { "title": "The Lord of the Rings", "author": "J. R. R. Tolkien", "price": 22.99 }
    ],
    "bicycle": { "color": "red", "price": 19.95 }
  }
}

Cztery książki z tytułem, autorem i ceną, plus rower. Zapamiętaj: ceny to 8.95, 12.99, 8.99 i 22.99 — i to one decydują o wynikach filtrów w dalszej części.

Korzeń, składnik potomny i zejście rekurencyjne ($ . ..)

Każde wyrażenie zaczyna się od korzenia, zapisywanego jako $. Stamtąd wchodzisz do składników potomnych kropką albo notacją nawiasową — obie są równoważne:

$.store.book          → the book array
$['store']['book']    → identical result

Notacja nawiasowa jest niezbędna, gdy klucz zawiera spacje, kropki lub inne znaki specjalne: $['first name'] działa tam, gdzie $.first name by nie zadziałało.

Operator .. to zejście rekurencyjne. Przeszukuje każdy poziom dokumentu, nie tylko bezpośrednie składniki potomne, i zbiera wszystko, co pasuje do kolejnego selektora na dowolnej głębokości:

$..author
→ ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"]

Kiedy używać .., a kiedy pełnej ścieżki

Zejście rekurencyjne jest wygodne, ale działa zgrubnie. $..price dopasowuje każdą cenę w dowolnym miejscu drzewa — w tym store.bicycle.price, której być może nie chciałeś. Gdy znasz kształt danych, wypisz ścieżkę wprost, żeby zapytanie pozostało precyzyjne:

$..price                  → [8.95, 12.99, 8.99, 22.99, 19.95]  (includes the bicycle)
$.store.book[*].price     → [8.95, 12.99, 8.99, 22.99]         (only books)

.. zarezerwuj dla naprawdę nieregularnych lub nieznanych struktur. To wymiana wygody na kontrolę: im więcej wiesz o swoich danych, tym bardziej powinieneś preferować jawną ścieżkę.

Symbole wieloznaczne, indeksy i wycinki tablic (* [0] [start:end:step])

Symbol wieloznaczny * wybiera wszystkie elementy tablicy lub wszystkie składniki obiektu:

$.store.book[*].title
→ ["Sayings of the Century", "Sword of Honour", "Moby Dick", "The Lord of the Rings"]

Indeksy tablicy liczone są od zera, a indeksy ujemne liczą się od końca:

$.store.book[0].title     → ["Sayings of the Century"]
$.store.book[-1].title    → ["The Lord of the Rings"]

Wycinki stosują tę samą półotwartą konwencję [start:end:step] co Python i JavaScript: start jest włączony, end wykluczony.

$.store.book[0:2].title   → ["Sayings of the Century", "Sword of Honour"]

To zwraca dwie książki, nie trzy — indeks 0 i indeks 1, zatrzymując się przed indeksem 2. Wykluczająca granica końcowa to najczęstszy błąd w JSONPath, więc warto wbić to sobie do pamięci:

[0:2]   → first TWO elements (indices 0, 1)   ← correct
[0:3]   → first THREE elements (indices 0, 1, 2)

Pomiń granicę, żeby dojść do krawędzi, i dodaj krok, żeby wybierać co N-ty element:

$.store.book[2:].title    → ["Moby Dick", "The Lord of the Rings"]
$.store.book[:3].title    → first three titles
$.store.book[::2].title   → ["Sayings of the Century", "Moby Dick"]  (every other)

Wyrażenia filtrujące [?()] — najmocniejsza część

To w filtrach JSONPath naprawdę zarabia na siebie. Filtr [?()] zatrzymuje tylko te elementy, dla których predykat jest prawdziwy, a wewnątrz filtra @ odnosi się do aktualnie testowanego elementu.

Aby wybrać książki tańsze niż 10:

$.store.book[?(@.price < 10)].title
→ ["Sayings of the Century", "Moby Dick"]

Wobec cen z księgarni (8.95, 12.99, 8.99, 22.99) kwalifikują się dwie książki. Oto jak krok po kroku budować predykaty filtra:

  1. Porównaj z literałem. Użyj ==, !=, <, <=, >, >= — na przykład @.price > 10.
  2. Dopasuj łańcuch znaków. Literały tekstowe biorą się w pojedyncze cudzysłowy: @.author == 'Nigel Rees'.
  3. Sprawdź istnienie. Samo odwołanie do składnika wybiera elementy, które go mają: [?(@.isbn)] zatrzymuje tylko książki z isbn.
  4. Połącz warunki. Łącz predykaty przez && i ||: [?(@.price < 10 && @.author == 'Herman Melville')].

Najczęstszy błąd w filtrach dotyczy zasięgu @. Wewnątrz predykatu bieżącym elementem jest @, a nie $. Zapis $.price wskazuje z powrotem na korzeń dokumentu, a nie na testowaną książkę:

$.store.book[?($.price < 10)]   → wrong scope, matches nothing useful
$.store.book[?(@.price < 10)]   → correct: each book's own price

RFC 9535 a Classic w filtrach

Oba dialekty rozchodzą się przy spacjach i cudzysłowach. Classic jest pobłażliwy — [?(@.price<10)] bez spacji parsuje się bez problemu. RFC 9535 trzyma się swojej gramatyki dosłownie i jest bardziej rygorystyczny co do zapisu filtra. Jeśli filtr, który gdzie indziej działał, zawodzi, sprawdź odstępy i silnik. Trzymaj filtry w czystości (operatory oddzielone spacjami, łańcuchy w pojedynczych cudzysłowach), a będą ewaluować tak samo niezależnie od tego, która biblioteka je ostatecznie uruchomi.

Selektory sumy — wybierz wiele kluczy naraz ([a,b])

Selektor sumy wymienia kilka nazw lub indeksów w jednym nawiasie i zbiera je wszystkie:

$.store.book[0]['title','author']
→ ["Sayings of the Century", "Nigel Rees"]

Sumy działają również z indeksami, a do stałej projekcji można je mieszać z innymi selektorami:

$.store.book[0,2].title          → ["Sayings of the Century", "Moby Dick"]
$.store.book[*]['title','price']  → title and price of every book

Sumy to właściwe narzędzie, gdy chcesz kilku konkretnych pól, a nie całego obiektu czy zamaszystego przejścia symbolem wieloznacznym.

Funkcje RFC 9535: length, count, match, search, value

RFC 9535 definiuje pięć standardowych rozszerzeń funkcyjnych. Reguła, na której potyka się niemal każdy — i którą konkurencyjne przewodniki notorycznie podają błędnie — brzmi tak:

Te funkcje można wywoływać wyłącznie wewnątrz filtra [?...], nigdy jako samodzielny segment ścieżki.

Zapis $.store.book.length() nie jest poprawnym RFC 9535 i standardowa gramatyka go odrzuca. Ta forma wywołania w segmencie to rozszerzenie jsonpath-plus, nie część specyfikacji. Aby filtrować po długości, wywołujesz funkcję wewnątrz predykatu:

$.store.book[?length(@.title) > 15]
→ [
    { "title": "Sayings of the Century", "author": "Nigel Rees", "price": 8.95 },
    { "title": "The Lord of the Rings", "author": "J. R. R. Tolkien", "price": 22.99 }
  ]

Oba wybrane tytuły są dłuższe niż 15 znaków; „Moby Dick” (9) oraz „Sword of Honour” (15, a nie ponad 15) zostają wykluczone.

Oto co robi każda funkcja wewnątrz filtra:

  • length() — długość łańcucha, tablicy lub obiektu: [?length(@.title) > 15]
  • count() — liczba węzłów na liście węzłów: [?(count(@.authors) > 1)]
  • match() — test wyrażeniem regularnym na całym łańcuchu (wzorzec I-Regexp): [?match(@.author, 'J.*')]
  • search() — test wyrażeniem regularnym na podłańcuchu: [?search(@.title, 'the')]
  • value() — zamienia jednowęzłową listę węzłów na jej wartość do porównania

Cała piątka to funkcjonalność RFC 9535. Dialekt Classic (Goessner) ich nie implementuje, więc jeśli wyrażenie oparte na funkcji zawodzi, upewnij się, że wywołujesz ją wewnątrz filtra i że silnik jest ustawiony na RFC 9535.

RFC 9535 a Classic Goessner — dlaczego to samo wyrażenie daje inny wynik

Gdy wyrażenie JSONPath zwraca różne wyniki w dwóch narzędziach, zwykle winny jest dialekt. Oto jak oba wypadają w porównaniu:

AspektClassic Goessner (2007)RFC 9535 (2024)
StandaryzacjaDe facto, nigdy nie sformalizowanyPierwsza formalna specyfikacja IETF
Spacje/cudzysłowy w filtrachPobłażliwy ([?(@.price<10)] OK)Rygorystyczny, dosłownie wg gramatyki
Porównanie brakującego składnikaZależne od implementacjiJednoznaczne, nie rzuca wyjątku
Funkcje standardowePoza dialektemlength count match search value
Ścieżki znormalizowaneBrak postaci kanonicznejKanoniczna, nawiasowa z pojedynczym cudzysłowem
Kolejność sumyRóżna w zależności od bibliotekiOkreślona

Praktyczna rada: jeśli twój system docelowy deklaruje zgodność z RFC 9535, pisz i waliduj wobec standardowego silnika. Jeśli utrzymujesz wyrażenie skopiowane z jsonpath.com, jsonpath-plus albo usługi opartej na Jayway, użyj Classic, żeby wyniki dało się odtworzyć. JSONPath Tester uruchamia oba silniki za jednym przełącznikiem, więc możesz wkleić wyrażenie raz i zobaczyć obok siebie, jak każdy dialekt je obsługuje — to porównanie dwóch silników jest najszybszym sposobem na zdiagnozowanie rozbieżności.

JSONPath a XPath a jq — czego użyć

Tę trójkę często się myli, więc oto wersja skrócona:

  • JSONPath to deklaratywne zapytanie ścieżkowe dla JSON. Najlepiej sprawdza się osadzone w konfiguracjach i asercjach testów, gdzie chcesz nazwać lokalizację bez pisania kodu.
  • XPath to odpowiednik ze świata XML. JSONPath zapożyczył część jego notacji (*, .., []), dlatego analogia się trzyma, ale języki nie są wymienne, a zestawy funkcji się różnią.
  • jq to przetwarzacz JSON z wiersza poleceń. Wykracza daleko poza wybór ścieżek — transformacja, agregacja, przekształcanie kształtu — i żyje w twoim potoku powłoki.

Decyzja zwykle jest prosta. Do osadzonej asercji albo pola konfiguracji potoku sięgnij po JSONPath. Do transformacji i obróbki danych sterowanej z powłoki sięgnij po jq — ściąga do jq szczegółowo omawia ten przepływ pracy. A gdy pytanie brzmi, czy payload odpowiada oczekiwanemu kształtowi, a nie gdzie leży pole, zwaliduj go za pomocą narzędzia JSON Schema Validator i jego pełnego przewodnika po walidacji.

7 częstych błędów w JSONPath

  1. Zapomniany korzeń $. store.book jest odrzucane przez większość silników; każde wyrażenie zaczyna się od $.
  2. Wycinek o jeden za dużo. [0:2] to dwa elementy, nie trzy — granica końcowa jest wykluczająca.
  3. Niewłaściwy dialekt. Uruchomienie wyrażenia Classic pod RFC 9535 (lub odwrotnie) może dać błąd parsowania albo dopasować inne węzły. Przełącz silnik, żeby pasował.
  4. Funkcja jako samodzielny segment. $.store.book.length() jest niepoprawne w RFC 9535; wywołaj length() wewnątrz filtra.
  5. Zapomniany @ w filtrze. [?($.price < 10)] wskazuje na korzeń; użyj [?(@.price < 10)].
  6. Złe cudzysłowy w nawiasie. $[store] to błąd; weź klucz w cudzysłów: $['store'].
  7. Założenie, że .. zatrzymuje się na pierwszym poziomie. Zejście rekurencyjne dopasowuje na każdej głębokości, nie tylko bezpośrednie składniki potomne.

Testuj JSONPath online i prywatnie

Najszybszy sposób na naukę składni JSONPath to uruchamianie jej. JSONPath Tester ewaluuje na żywo każde wyrażenie z tego przewodnika: dwa silniki RFC 9535 i Classic, widoki wyników Values / Paths / Both, ścieżki znormalizowane do debugowania oraz wykonanie w 100% w przeglądarce — bez wysyłania danych, bez rejestracji i bez eval, więc jest bezpieczne dla zastrzeżonych payloadów. Zbuduj ścieżkę tutaj, potwierdź, że wybiera dokładnie te węzły, których chcesz, a potem wklej zwalidowane wyrażenie wprost do swojego kodu, testów lub potoku.

W pozostałej części przepływu pracy z JSON zamień przykładową odpowiedź w typowane interfejsy za pomocą JSON to TypeScript albo porównaj dwa dokumenty pole po polu narzędziem JSON Diff.

Najczęściej zadawane pytania

Do czego służy JSONPath?

JSONPath odpytuje JSON bez imperatywnego kodu. Programiści używają go do wyciągania pól z odpowiedzi API, do asercji na wartościach w testach integracyjnych oraz do adresowania pól w konfiguracjach dla Kubernetes, AWS Step Functions i Azure Logic Apps. Błyszczy przy wydobywaniu danych z dużych lub nieregularnych struktur, gdzie ręczne pisanie przejścia byłoby żmudne.

Jaka jest różnica między RFC 9535 a klasycznym JSONPath?

Classic to dialekt de facto Stefana Goessnera z 2007 roku — szeroko zaimplementowany, ale nigdy formalnie wyspecyfikowany, więc biblioteki się rozeszły. RFC 9535 to formalna specyfikacja IETF z lutego 2024 roku: definiuje precyzyjną gramatykę, ścieżki znormalizowane dla wyników i pięć standardowych funkcji. Oba różnią się na krawędziach w filtrach, sumach i porównaniu brakującego składnika.

Jak działają wyrażenia filtrujące JSONPath?

Filtr [?()] zatrzymuje tylko te elementy, których predykat jest prawdziwy, a @ to bieżący element. Na przykład $.store.book[?(@.price < 10)] wybiera książki w cenie poniżej 10. Możesz łączyć warunki przez && i ||, sprawdzać, czy składnik istnieje, oraz porównywać z literałami tekstowymi lub liczbowymi.

Czy mogę użyć length() jako $.store.book.length()?

Nie. W RFC 9535 length() i pozostałe cztery funkcje można wywoływać wyłącznie wewnątrz filtra, jak $.store.book[?length(@.title) > 15]. Samodzielna forma segmentu $.store.book.length() to rozszerzenie jsonpath-plus, a nie standardowy JSONPath, i gramatyka RFC 9535 ją odrzuca.

Czy JSONPath to to samo co XPath?

Nie, ale idea jest podobna. XPath odpytuje XML, JSONPath odpytuje JSON, a oba lokalizują węzły za pomocą selektorów ścieżki. JSONPath celowo zapożyczył część notacji XPath — *, .. i [] — co czyni analogię użyteczną, ale składnia, semantyka i zestawy funkcji są różne i niewymienne.

Co robi zejście rekurencyjne (..) w JSONPath?

Operator .. przeszukuje każdy poziom dokumentu, nie tylko bezpośrednie składniki potomne. $..author zbiera każdy składnik author, gdziekolwiek się pojawia, na dowolnej głębokości zagnieżdżenia. To najszybszy sposób na wyciągnięcie jednego pola z głęboko zagnieżdżonej lub nieregularnej struktury, ale potrafi dopasować znacznie więcej węzłów, niż się spodziewasz — zawężaj go, kiedy tylko możesz.

Tagi: jsonpath json query-language rfc-9535 filter-expression developer-tools

Powiązane artykuły

Zobacz wszystkie artykuły