JSON Toolkit

Мова запитів JSONPath на практиці

Головне: У 2024 році JSONPath нарешті отримав формальний стандарт, RFC 9535. Ця стаття пояснює синтаксис, семантику фільтрів, функції й історичні відмінності між реалізаціями.

Середній~12 хв читанняОпубліковано 2026-10-30Оновлено 2026-10-30
Обкладинка статті «Мова запитів JSONPath на практиці»

Коротко: спершу локалізуйте явним шляхом, а фільтр накладайте поверх, і пишіть вирази за RFC 9535; використовуйте доступ до властивості або JSON Pointer для одного фіксованого поля, а jq чи код — для агрегації.

Ви, мабуть, бачили вирази на кшталт $.store.book[?(@.price < 10)].title, але міжнародним стандартом вони стали лише 2024 року.

1. Від де-факто стандарту до формального стандарту

JSONPath запропонував Стефан Гьосснер 2007 року, і довгий час єдиною «специфікацією» був один допис у блозі, а кожна мовна реалізація робила своє:

  • чи потребують фільтри дужок (?(@.a) проти ?@.a);
  • чи підтримується індекс масиву [-1];
  • чи повертає результат «значення» чи «значення й шляхи»;
  • чи дозволені скриптові вирази (?(@.price < $.max)).

RFC 9535, опублікований у лютому 2024, звів ці розбіжності до єдиного синтаксису й семантики та надав офіційний набір тестів відповідності. Нові проєкти мають слідувати RFC 9535; під час міграції старих проєктів спершу підтвердьте версію реалізації.

2. Шпаргалка синтаксису

Синтаксис Значення Приклад
$ Кореневий вузол $.store
@ Поточний вузол усередині фільтра $[?@.price < 10]
.name Дочірній вузол (назва має відповідати [A-Za-z_][A-Za-z0-9_]*) $.store.book
['name'] Дочірній вузол (будь-яка назва, включно з не-ASCII і крапками) $['store']['书']
[0] Індекс масиву; відʼємні числа рахуються з кінця $.book[0], $.book[-1]
[0,2] Кілька індексів / кілька назв $.book[0,2]
[1:3] Зріз, включний початок і виключний кінець $.book[1:3]
[::2] Зріз із кроком $.book[::2]
[*] Шаблон усіх дочірніх вузлів $.book[*].title
..name Рекурсивний спуск, пошук на будь-якій глибині $..price
..[*] Рекурсивно взяти всі вузли $..*
[?<expr>] Селектор фільтра $.book[?@.price < 10]

Одна семантика, яку треба запамʼятати: JSONPath повертає набір вузлів (nodelist), а не одне значення. Навіть коли збігається лише один вузол, результат — список довжини один.

3. Прогін синтаксису на одному зразку

{
  "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
}
Вираз Результат
$.store.bicycle.color ["red"]
$..price 4 ціни (включно з ціною велосипеда)
$.store.book[*].title 3 назви
$.store.book[?@.price < 10].title Назви книжок 1 і 3
$.store.book[?@.tags] Усі книжки, що мають tags (перевірка існування)
$.store.book[-1].title Назва останньої книжки
$..book[0,1] Перші дві книжки
$.store.book[?@.price < $.expensive] Синтаксично залежить від реалізації; RFC 9535 не дозволяє посилання поза коренем

Останній рядок — історична пастка: багато реалізацій дозволяють $.expensive усередині фільтра для порівняння між вузлами, що RFC 9535 прямо забороняє — фільтр може звертатися лише до поточного вузла @. Коли треба порівняти з глобальним порогом, передайте поріг як аргумент функції запиту.

4. Що можуть виражати фільтри

Вирази фільтрів RFC 9535 підтримують чотири види конструкцій:

Logic:       &&  ||  !          e.g. ?@.price < 10 && @.tags
Comparison:  ==  !=  <  <=  >  >=
Existence:   a bare path        e.g. ?@.tags (tests existence)
Functions:   length() count() match() search() value()

Кілька практичних прикладів:

// Ціна між 8 і 10
$.store.book[?@.price >= 8 && @.price <= 10]

// Назва містить "JSON" (збіг регулярного виразу; другий аргумент — I-Regexp)
$.store.book[?match(@.title, ".*JSON.*")]

// Більше одного тегу
$.store.book[?length(@.tags) > 1]

// Отримати перше знайдене значення замість списку (value() перетворює одноелементний набір на значення)
$.store.book[?@.price < 9].title.value()

match() — це збіг усього рядка, а search() — пошук підрядка; базовий діалект регулярних виразів — I-Regexp (підмножина RFC 9485), яка не підтримує ні зворотних посилань, ні lookaround — це водночас обмеження й зручний щит проти DoS на регулярних виразах.

5. Відмінності реалізацій: перевіряйте перед міграцією

Реалізація Мова Підтримка RFC 9535 Примітки
jsonpath-plus JavaScript Здебільшого сумісна Підтримує функції розширення й скриптові вирази; потужніша, але не строго стандартна
jsonpath (dchester) JavaScript Часткова Давня бібліотека з синтаксисом ?() та іншою структурою результатів
ojg / gjson Go ojg має добру підтримку gjson використовує інший синтаксис шляхів і не є JSONPath
jsonpath-ng Python Часткова Підтримує розширення й досить ліберальна
Jayway JsonPath Java Часткова Широка екосистема; історична поведінка різниться від RFC

Три перевірки перед міграцією:

  1. Чи обовʼязкові дужки фільтрів;
  2. Чи підтримуються відʼємні індекси на кшталт -1;
  3. Чи результат — «масив значень» чи «масив обʼєктів {path, value}».

Третє найлегше спіткнутися: фронтенд читає result[0], а після зміни бібліотеки отримує обʼєкт, і сторінка залишається порожньою.

6. Продуктивність і безпека

Продуктивність

  • JSONPath по суті є одним обходом зі складністю O(кількість вузлів), і не може використовувати індекси;
  • Рекурсивний спуск через .. найдорожчий; якщо можете спершу локалізувати через $.store.book, а потім фільтрувати, не пишіть $..book;
  • Компілюйте вирази один раз і повторно використовуйте (більшість бібліотек пропонують compile або кешування) замість повторного розбору на кожен запит;
  • Для документів із понад 100 000 вузлів розгляньте спершу точне вилучення через JSON Pointer або попередню обробку на сервері через jq.

Безпека

  • Вирази з користувацького введення мають мати обмеження довжини й глибини вкладеності;
  • Не дозволяйте JSONPath керувати авторизацією — він може лише читати й не може виразити «які поля невидимі»;
  • Коли регулярний вираз, що використовується в match(), походить від користувачів, обмежте його складність; хоча він безпечніший за PCRE, обмеження довжини все одно рекомендовано.

7. Коли не використовувати JSONPath

  • Вам потрібне лише одне фіксоване поле → використовуйте безпосередньо data.store.bicycle.color;
  • Вам треба записати назад після зміни → використовуйте JSON Pointer або патч; JSONPath не пише назад;
  • Вам потрібна агрегація (підсумовування, групування) → використовуйте jq, SQL або код; JSONPath лише вибирає;
  • Документ величезний, а вам треба лише кілька полів → використовуйте потоковий парсер, а не читайте все дерево в памʼять спершу.

8. Чек-лист

  • [ ] Вирази слідують RFC 9535 і не покладаються на посилання поза коренем;
  • [ ] Ясно, що результат — набір вузлів; використовуйте [0] або value() для одного значення;
  • [ ] Вирази попередньо скомпільовані й повторно використовуються;
  • [ ] Контрольовані користувачем вирази мають обмеження довжини й складності;
  • [ ] Офіційні тести відповідності запускаються під час зміни реалізації.

Часті питання

Як обирати між JSON Pointer і JSONPath?

Використовуйте звичайний доступ до властивості або JSON Pointer, коли потрібне лише одне фіксоване поле; звертайтеся до JSONPath, коли потрібні фільтри, шаблони чи рекурсивний спуск. JSONPath лише вибирає й ніколи не пише назад, тож запис назад належить JSON Pointer або патчу.

Чому фільтр не може посилатися на $.expensive?

RFC 9535 прямо забороняє посилання поза коренем; фільтр може звертатися лише до поточного вузла @. Натомість передайте поріг як аргумент функції запиту. Старіші реалізації приймають цей синтаксис, але він ламається, коли ви змінюєте чи оновлюєте бібліотеки.

Чи покладатися на result[0] для читання значення?

Лише після підтвердження, що поточна реалізація повертає масив значень. Деякі бібліотеки повертають обʼєкти {path, value}, тож result[0] стає обʼєктом, і сторінка стає порожньою. Читайте окремі значення через [0] або value() і запускайте офіційний набір тестів відповідності перед зміною реалізації.

Джерела

Відповідні інструменти

Схожі статті

JSON Pointer, Patch і Merge Patch

Три короткі специфікації утворюють протокол зміни: Pointer локалізує, Patch редагує точно, Merge Patch зливає частково. Неправильний вибір боляче бʼє по масивах і конкурентності.

Середній~12 хв читанняОновлено 2026-10-30

Порівняння бібліотек розбору й обробки JSON

Нативні API найшвидші, толерантні парсери дають зміщення. Обирайте за трьома ознаками: локалізація помилок, чи зберігається оригінальний текст і чи межі явні.

Середній~11 хв читанняОновлено 2026-10-30

Фільтри JSONPath поглиблено: рекурсія, зрізи й зіставлення кількох умов

Рекурсивний спуск, зрізи й фільтри з кількома умовами — саме там JSONPath стає потужним і неправильно використовуваним. Охоплює семантику фільтрів, форми результатів і відмінності бібліотек.

Середній~10 хв читанняОновлено 2026-10-31