Dotazovací jazyk JSONPath v praxi
Shrnutí: V roce 2024 dostal JSONPath konečně formální standard, RFC 9535. Tento článek vysvětluje syntaxi, sémantiku filtrů, funkce a historické rozdíly mezi implementacemi.

Stručně: nejprve lokalizujte explicitní cestou a filtr stavěte nad ní a výrazy pište proti RFC 9535; pro jediné pevné pole použijte přístup k vlastnosti nebo JSON Pointer a pro agregaci jq či kód.
Výrazy jako
$.store.book[?(@.price < 10)].titlejste pravděpodobně viděli, ale mezinárodním standardem se to stalo až v roce 2024.
1. Od de facto standardu k formálnímu standardu
JSONPath navrhl Stefan Gössner v roce 2007 a dlouho byla jedinou „specifikací“ jediná blogová položka, přičemž každá jazyková implementace si dělala své:
- zda filtry potřebují závorky (
?(@.a)vs?@.a); - zda je podporován index pole
[-1]; - zda výsledek vrací „hodnoty“, nebo „hodnoty a cesty“;
- zda jsou povoleny skriptové výrazy (
?(@.price < $.max)).
RFC 9535, publikované v únoru 2024, tyto rozdíly sjednotilo do jediné syntaxe a sémantiky a poskytlo oficiální konformní testovací sadu. Nové projekty by měly sledovat RFC 9535; při migraci starých projektů nejprve potvrďte verzi implementace.
2. Tahák syntaxe
| Syntaxe | Význam | Příklad |
|---|---|---|
$ |
Kořenový uzel | $.store |
@ |
Aktuální uzel uvnitř filtru | $[?@.price < 10] |
.name |
Podřízený uzel (název musí odpovídat [A-Za-z_][A-Za-z0-9_]*) |
$.store.book |
['name'] |
Podřízený uzel (libovolný název, včetně non-ASCII a teček) | $['store']['书'] |
[0] |
Index pole; záporná čísla počítají od konce | $.book[0], $.book[-1] |
[0,2] |
Více indexů / více názvů | $.book[0,2] |
[1:3] |
Slice, včetně začátku a bez konce | $.book[1:3] |
[::2] |
Slice s krokem | $.book[::2] |
[*] |
Zástupný znak pro všechny podřízené uzly | $.book[*].title |
..name |
Rekurzivní sestup, hledání v libovolné hloubce | $..price |
..[*] |
Rekurzivně vezmi všechny uzly | $..* |
[?<expr>] |
Filtrační selektor | $.book[?@.price < 10] |
Jednu sémantiku si musíte zapamatovat: JSONPath vrací množinu uzlů (nodelist), nikoli jedinou hodnotu. I když odpovídá jen jeden uzel, výsledkem je seznam délky jedna.
3. Průchod syntaxí na jednom vzorku
{
"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
}
| Výraz | Výsledek |
|---|---|
$.store.bicycle.color |
["red"] |
$..price |
4 ceny (včetně ceny kola) |
$.store.book[*].title |
3 názvy |
$.store.book[?@.price < 10].title |
Názvy knih 1 a 3 |
$.store.book[?@.tags] |
Všechny knihy, které mají tags (test existence) |
$.store.book[-1].title |
Název poslední knihy |
$..book[0,1] |
První dvě knihy |
$.store.book[?@.price < $.expensive] |
Syntakticky závislé na implementaci; RFC 9535 nepovoluje reference napříč kořenem |
Poslední řádek je historická past: mnoho implementací povoluje $.expensive uvnitř filtru pro srovnání napříč uzly, což RFC 9535 výslovně zakazuje –
filtr může přistupovat jen k aktuálnímu uzelu @. Když potřebujete srovnávat s globálním prahem, předejte práh jako argument funkci dotazu.
4. Co filtry umějí vyjádřit
Filtrační výrazy RFC 9535 podporují čtyři druhy konstrukcí:
Logika: && || ! např. ?@.price < 10 && @.tags
Porovnání: == != < <= > >=
Existence: pouhá cesta např. ?@.tags (testuje existenci)
Funkce: length() count() match() search() value()
Několik praktických příkladů:
// Cena mezi 8 a 10
$.store.book[?@.price >= 8 && @.price <= 10]
// Název obsahuje "JSON" (regex match; druhý argument je I-Regexp)
$.store.book[?match(@.title, ".*JSON.*")]
// Více než jeden tag
$.store.book[?length(@.tags) > 1]
// Získej první nalezenou hodnotu místo seznamu (value() mění jednočlennou množinu na hodnotu)
$.store.book[?@.price < 9].title.value()
match() je párování celého řetězce a search() je hledání podřetězce; podkladovým dialektem regulárních výrazů je I-Regexp (podmnožina RFC 9485),
který nepodporuje zpětné reference ani lookaround – což je omezení i praktický štít proti regex DoS.
5. Rozdíly mezi implementacemi: zkontrolujte před migrací
| Implementace | Jazyk | Podpora RFC 9535 | Poznámky |
|---|---|---|---|
jsonpath-plus |
JavaScript | Z většiny kompatibilní | Podporuje rozšiřující funkce a skriptové výrazy; schopnější, ale ne striktně standardní |
jsonpath (dchester) |
JavaScript | Částečná | Dlouhodobá knihovna se syntaxí ?() a jinou strukturou výsledku |
ojg / gjson |
Go | ojg má dobrou podporu |
gjson používá jinou syntaxi cest a není JSONPath |
jsonpath-ng |
Python | Částečná | Podporuje rozšíření a je poměrně benevolentní |
Jayway JsonPath |
Java | Částečná | Široký ekosystém; historické chování se liší od RFC |
Tři kontroly před migrací:
- Zda jsou vyžadovány závorky filtrů;
- Zda jsou podporovány záporné indexy jako
-1; - Zda je výsledkem „pole hodnot“, nebo „pole objektů
{path, value}“.
Třetí je nejsnadnější zakopnutí: frontend čte result[0] a po přepnutí knihovny dostane objekt, stránka zůstane prázdná.
6. Výkon a bezpečnost
Výkon
- JSONPath je v podstatě jeden průchod se složitostí O(počet uzlů) a nemůže používat indexy;
- Rekurzivní sestup pomocí
..je nejdražší; pokud můžete nejprve lokalizovat pomocí$.store.booka poté filtrovat, nepište$..book; - Výrazy zkompilujte jednou a znovu používejte (většina knihoven nabízí
compilenebo caching), místo abyste je znovu parsovali při každém dotazu; - U dokumentů s více než 100 000 uzly zvažte nejprve přesnou extrakci pomocí JSON Pointer, nebo předzpracování na serveru pomocí
jq.
Bezpečnost
- Výrazy ze vstupu uživatele musí mít limity délky a hloubky vnoření;
- Nenechte JSONPath hlídat autorizaci – umí jen číst a nedokáže vyjádřit „která pole jsou neviditelná“;
- Když regulární výraz používaný
match()pochází od uživatelů, omezte jeho složitost; i když je bezpečnější než PCRE, limit délky se stále doporučuje.
7. Kdy JSONPath nepoužívat
- Potřebujete jen jedno pevné pole → použijte přímo
data.store.bicycle.color; - Po úpravě potřebujete zapsat zpět → použijte JSON Pointer nebo patch; JSONPath nezapisuje zpět;
- Potřebujete agregaci (sčítání, seskupování) → použijte
jq, SQL nebo kód; JSONPath jen vybírá; - Dokument je obrovský a potřebujete jen několik polí → použijte streamovaný parser, místo abyste nejprve načetli celý strom do paměti.
8. Kontrolní seznam
- [ ] Výrazy sledují RFC 9535 a nespoléhají na reference napříč kořenem;
- [ ] Je jasné, že výsledkem je množina uzlů; pro jedinou hodnotu použijte
[0]nebovalue(); - [ ] Výrazy jsou předkompilované a znovu používané;
- [ ] Výrazy ovládané uživatelem mají limity délky a složitosti;
- [ ] Při přepínání implementací se spouštějí oficiální konformní testovací případy.
Časté dotazy
Jak volit mezi JSON Pointer a JSONPath?
Když potřebujete jen jedno pevné pole, použijte prostý přístup k vlastnosti nebo JSON Pointer; JSONPath sáhněte, když potřebujete filtry, zástupné znaky nebo rekurzivní sestup. JSONPath jen vybírá a nikdy nezapisuje zpět, takže zápis zpět patří JSON Pointeru nebo patchu.
Proč filtr nemůže odkazovat na $.expensive?
RFC 9535 výslovně zakazuje reference napříč kořenem; filtr může přistupovat jen k aktuálnímu uzelu @. Předejte práh jako argument funkci dotazu. Starší implementace syntaxi přijímají, ale rozbije se, když přepnete nebo upgradujete knihovnu.
Mám spoléhat na result[0] při čtení hodnoty?
Jen po potvrzení, že aktuální implementace vrací pole hodnot. Některé knihovny vracejí objekty {path, value}, takže result[0] se stane objektem a stránka zůstane prázdná. Jednotlivé hodnoty čtěte přes [0] nebo value() a před přepnutím implementací spusťte oficiální konformní sadu.