JSON Toolkit

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.

Středně pokročilý~12 min čteníPublikováno 2026-10-30Aktualizováno 2026-10-30
Titulní obrázek článku Dotazovací jazyk JSONPath v praxi

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)].title jste 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í:

  1. Zda jsou vyžadovány závorky filtrů;
  2. Zda jsou podporovány záporné indexy jako -1;
  3. 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.book a poté filtrovat, nepište $..book;
  • Výrazy zkompilujte jednou a znovu používejte (většina knihoven nabízí compile nebo 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] nebo value();
  • [ ] 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.