Filtry JSONPath podrobně: rekurze, slices a párování více podmínek
Shrnutí: Rekurzivní sestup, slices a filtry s více podmínkami jsou místa, kde JSONPath získává sílu a kde se zneužívá. Pokrývá sémantiku filtrů, tvary výsledků a rozdíly mezi knihovnami.

Stručně: nejprve lokalizujte známou hloubku, poté přidejte slices a filtry, místo abyste sahali po $..; pouhá cesta ve filtru testuje jen existenci, takže boolean testujte pomocí @.stock == true.
$.store.book[0].titleumí napsat každý. Co JSONPath skutečně rozbíjí, je kombinace$..se slices a více podmínkami, kdy dostanete příliš mnoha výsledků, příliš málo výsledků, nebo pořadí, které jste nečekali.
1. Vzorový dokument
{
"store": {
"book": [
{ "title": "JSON: The Definitive Guide", "price": 8.95, "stock": true, "tags": ["json", "spec"] },
{ "title": "A History of Data Formats", "price": 12.99, "stock": false, "tags": ["history"] },
{ "title": "How Parsers Work", "price": 8.99, "stock": true, "tags": ["parser", "json"] },
{ "title": "Streaming JSON in Practice", "price": 9.5, "stock": true, "tags": [] }
],
"bicycle": { "color": "red", "price": 19.95 }
},
"updatedAt": "2026-10-31"
}
Každý výraz níže používá tento dokument jako vstup. Mějte na paměti dva předpoklady: výsledkem je množina uzlů a její pořadí sleduje pořadí dokumentu, nikoli pořadí, v jakém byly podmínky napsány. Uvnitř filtru @ odkazuje jen na aktuální kandidátní uzel.
2. Rekurzivní sestup $..: pohodlný, ale drahý
.. znamená „hledej rekurzivně v libovolné hloubce z aktuální pozice“. RFC 9535 ho definuje jako sestupový segment a lze ho kombinovat s názvy, zástupnými znaky, indexy, slices a filtry:
$..price // každý člen jménem price v libovolné hloubce
$..book[?(@.price < 10)] // každá kniha v libovolné hloubce, poté filtr
$..[?(@.stock)] // jakýkoli uzel v libovolné hloubce, kde stock existuje
Tři věci k pochopení:
$..booknajde každý uzel jménembook, bez ohledu na hloubku;- Procházení je do hloubky a pořadí výstupu závisí na tom, jak implementace navštěvuje potomky;
..nemůže použít žádný index, takže jeho náklad škáluje s celkovým počtem uzlů.
Výsledkem je, že $..price a $.store.book[*].price se mohou ve výstupu lišit jen cenou kola, ale v nákladech se liší o řád. Když znáte hloubku, vždy pište explicitní cestu. Pokud chcete jen srovnat hranice dvou jazyků cest, viz JSONPath vs JSON Pointer.
3. Slices a zástupné znaky polí: tři pasti
Syntaxe slice je [start:end:step], včetně začátku a bez konce, se zápornými čísly počítajícími od ocasu:
| Výraz | Význam | Výsledek (se čtyřmi knihami) |
|---|---|---|
[0:2] |
První dvě knihy | Knihy 1 a 2 |
[1:] |
Od druhé knihy do konce | Knihy 2, 3 a 4 |
[:2] |
První dvě knihy | Knihy 1 a 2 |
[-2:] |
Poslední dvě knihy | Knihy 3 a 4 |
[::2] |
Každá druhá kniha | Knihy 1 a 3 |
[::-1] |
Opačné pořadí | Knihy 4, 3, 2 a 1 |
Tři pasti:
- Krok nula je neplatný. RFC 9535 vyžaduje nenulový krok, zatímco některé implementace tiše vrací prázdnou množinu;
- Záporné kroky nejsou univerzálně podporovány. Původní implementace Goessnera je podporuje, ale některé minimalistické implementace přijímají jen kladné kroky;
- Filtrování po slice nerozšiřuje rozsah.
[0:2][?(@.stock)]filtruje jen dvě knihy produkované slice; nevrací se k celému poli.
Zástupný znak [*] lze kombinovat se slices, ale [*] vydává „všechny podřízené uzly“. Pokud jsou potomky objekty, přidání .title dá všechny názvy. Pokud je potomek sám pole, .title implicitně nerozbalí další úroveň. Když potřebujete rozbalit, napište [*] znovu; nečekejte implicitní zploštění.
4. Filtry s více podmínkami: porovnání, logika a existence
Filtr [?(...)] může obsahovat čtyři druhy konstrukcí: porovnání, logické operátory, testy existence a funkce. Běžný dotaz s více podmínkami vypadá takto:
$..book[?(@.price < 10 && @.stock)]
Znamená „cena pod 10 a stock je pravdivý“. Věnujte pozornost několika sémantickým detailům:
@.stockje test existence, nikoli „hodnota je true“. Pokud pole existuje, ale jeho hodnota jefalse, podmínka stále platí. Pro test boolean hodnoty musíte napsat@.stock == true;&&má vyšší prioritu než||. Při jejich míchání přidejte závorky, místo abyste se spoléhali na paměť;- Obě strany porovnání musí mít stejný typ. Čísla a řetězce se implicitně nepřevádějí;
@.price < "10"je ve striktních implementacích false nebo chyba; - Chybějící pole vždy činí porovnání nepravdivým.
@.missing < 10nevyhodí chybu; prostě nic nenajde.
Toto je nejsnadnější místo zakopnutí, když filtry píšete, jako by šlo o JavaScript: if (obj.stock) testuje pravdivost v JavaScriptu, zatímco JSONPath testuje existenci. Kompletní standardní syntaxi a funkce najdete v Dotazovací jazyk JSONPath v praxi.
5. Skriptové výrazy: více síly, méně přenositelnosti
Původní implementace Goessnera a jsonpath-plus povolují srovnání napříč uzly jako [?(@.price < $.max)] a dokonce libovolné skripty. RFC 9535 výslovně povoluje jen přístup k aktuálnímu uzelu @ a zakazuje $ uvnitř filtru.
// Funguje ve starších implementacích; RFC 9535 to nepovoluje
$..book[?(@.price < $.expensive)]
// Standardní přístup: předejte práh od volajícího
query(document, '$..book[?(@.price < threshold)]', { threshold: 10 })
Skriptové výrazy vytvářejí tři problémy:
- Přenositelnost klesá na nulu. Přepnutí knihovny znamená přepsat každý výraz;
- Bezpečnostní hranice mizí. Když výrazy pocházejí od uživatelů, vyhodnocování skriptů předává spuštění kódu;
- Statická optimalizace se stává nemožnou. Implementace může jen interpretovat uzel po uzelu, což je pomalé a těžko cachovatelné.
Pokud skutečně potřebujete srovnání napříč uzly, předejte práh jako parametr dotazu a podruhé filtrujte v aplikaci. To je obvykle stabilnější než závislost na rozšiřující syntaxi.
6. Tvary výsledků: myslíte si, že dostáváte hodnoty, ale dostáváte uzly
Stejný výraz může napříč knihovnami vrátit alespoň tři tvary:
["JSON: The Definitive Guide", "How Parsers Work", "Streaming JSON in Practice"] // pole hodnot
[{ "path": "$['store']['book'][0]['title']", "value": "..." }] // cesta + hodnota
[{ "pointer": "/store/book/0/title", "value": "..." }] // JSON Pointer + hodnota
To přímo ovlivňuje tři věci: jak frontend čte výsledky, jak se výsledky deduplikují a jak se výsledky dotazu stanou zápisovými operacemi. Nodelist definovaný RFC 9535 je množina referencí na uzly, ale JavaScriptové implementace obvykle vracejí jen hodnoty, protože reference nemohou překračovat hranice procesů.
Praktická rada:
- Jednotlivou hodnotu vždy čtěte přes
[0]nebovalue(); nikdy nepředpokládejte, že výsledek je skalár; - Když potřebujete zapisovat zpět, vyberte implementaci, která vrací JSON Pointery, nebo pointer postavte sami;
- Prázdný výsledek a výsledek
[null]jsou různé; kontrolujte délku, nikoli pravdivost.
7. Rozdíly mezi knihovnami: před migrací spusťte konformní sadu
| Schopnost | Původní Goessner | jsonpath-plus | Striktní RFC 9535 |
|---|---|---|---|
Závorky filtru ?() |
Vyžadovány | Kompatibilní | Volitelné |
Reference napříč kořenem $.x |
Podporováno | Podporováno | Zakázáno |
| Skriptové výrazy | Podporováno | Podporováno | Zakázáno |
| Záporné indexy a kroky | Podporováno | Podporováno | Podporováno |
| Vracení cest | Konfigurovatelné | Konfigurovatelné | Nespecifikováno |
Funkce jako length() |
Žádné | Rozšíření | Standardní funkce |
Před migrací ověřte alespoň čtyři věci: zda jsou vyžadovány závorky, zda se $ smí objevit uvnitř filtru, jak se chovají záporné kroky a strukturu výsledku. Oficiální konformní testovací sada pokrývá většinu syntaktických rozdílů a brání tichým selháním typu „po přepnutí knihovny zmizely tři řádky“.
8. Výkon a kontrolní seznam zneužití
Výkon se shrnuje do jedné věty: JSONPath je úplný průchod a .. je nejdražší druh úplného průchodu.
- U dokumentů s více než 100 000 uzly nejprve zúžte rozsah explicitní cestou a poté filtrujte podmnožinu;
- Výraz zkompilujte jednou a znovu používejte; neparsujte řetězec znovu uvnitř smyčky;
- Pro agregaci (sčítání, seskupování, řazení) přejděte k
jqnebo kódu; JSONPath jen vybírá; - Podmínky dávejte co nejdřív:
$.store.book[?(@.price < 10)]skenuje mnohem méně irelevantních uzlů než$..[?(@.price < 10)].
Kontrolní seznam zneužití:
- [ ] Považovat test existence za test boolean pravdivosti;
- [ ] Používat
$..místo známé hloubky; - [ ] Spoléhat na skriptové výrazy pro srovnání napříč uzly;
- [ ] Předpokládat, že výsledek je vždy pole hodnot;
- [ ] Povolit výrazy ovládané uživatelem bez limitů délky a vnoření.
Časté dotazy
Proč @.stock stále odpovídá, když je pole false?
Protože pouhá cesta uvnitř filtru znamená „pole existuje“, nikoli „hodnota je true“. Pro test boolean hodnoty srovnejte explicitně pomocí @.stock == true. To je největší sémantický rozdíl mezi JSONPath a pravdivostí v JavaScriptu.
Je pořadí rekurzivního sestupu stabilní?
Ne. RFC 9535 specifikuje sémantiku množiny uzlů a pořadí průchodu závisí na tom, jak implementace navštěvuje potomky. Když potřebujete pevné pořadí, lokalizujte pole a čtěte podle indexu, nebo výsledky seřaďte v aplikaci.
Lze slices a filtry kombinovat?
Ano, ale pamatujte na rozsah. $.store.book[0:2][?(@.stock)] filtruje jen dvě knihy produkované slice. Pro nejprve filtrovat a poté slice napište $.store.book[?(@.stock)][0:2]; jiné pořadí dává jiný výsledek.
Proč jsonpath-plus podporuje $.expensive, když to standard zakazuje?
Protože filtry jsou definovány jen pomocí aktuálního uzlu @. Reference napříč kořenem činí výraz závislým na kontextu celého dokumentu, což je těžké staticky analyzovat a přenášet mezi jazyky. Předejte práh jako parametr, místo abyste ho vkládali do výrazu.
Jak zabránit rekurzivnímu sestupu skenovat irelevantní uzly?
Pište explicitní cestu, kdykoli znáte hloubku, nejprve zúžte rozsah a filtrujte jen podmnožinu, místo abyste používali $.. namísto známé úrovně. Podmínky dávejte co nejdřív: $.store.book[?(@.price < 10)] skenuje mnohem méně irelevantních uzlů než $..[?(@.price < 10)]. U dokumentů s více než 100 000 uzly udělejte toto první.