Тестер JSONPath

Проверка запросов JSONPath по RFC 9535 к JSON: селекторы, фильтры, все пять функций и нормализованный путь каждого совпадения — всё в браузере.

Запрос JSONPath
JSON
Совпадения: 2
  • Путь$['store']['book'][0]['title']
    "Sayings of the Century"
  • Путь$['store']['book'][2]['title']
    "Moby Dick"

Язык запросов, у которого наконец есть стандарт

JSONPath для JSON — то же, что XPath для XML: небольшой язык для указания на части документа. Вы пишете выражение вроде $.store.book[0].title, и оно выбирает соответствующие узлы. Идея пришла из записи в блоге Stefan Goessner 2007 года, и семнадцать лет эта запись была единственной ссылкой — из-за чего каждая библиотека заполняла пробелы по-своему. Что выбирает одиночный $..? Гарантированно ли $[1,2] возвращается по порядку? Как фильтр сравнивает отсутствующее значение? Спросите три библиотеки JSONPath и можете получить три ответа.

RFC 9535, опубликованный в 2024 году, наконец всё зафиксировал. Этот тестер нацелен на этот стандарт, а не на фольклор. Он выполняет запрос так, как говорит RFC, показывает каждое совпадение вместе с его точным местоположением и — что важно — отклоняет запрос, допустимый в каком-то старом диалекте, но не в RFC 9535, сообщая, где проблема, вместо того чтобы молча делать нечто нестандартное, что другой инструмент сделал бы иначе.

Сегменты и селекторы

Запрос — это корневой идентификатор $, за которым следует последовательность сегментов, и каждый сегмент применяет один или несколько селекторов к получаемым узлам. Есть пять селекторов:

  • Имя — $.store или $["store"] выбирает значение члена. Точечная форма — сокращение; форма со скобками и кавычками работает для любого ключа, в том числе с пробелами или пунктуацией.
  • Подстановка — * выбирает каждый член объекта или каждый элемент массива.
  • Индекс — [0] выбирает элемент массива, а отрицательный индекс отсчитывается с конца, так что [-1] — последний.
  • Срез (slice) — [start:end:step] выбирает диапазон, точно как в Python: [1:3] — это элементы 1 и 2, [::-1] переворачивает, [::2] берёт каждый второй.
  • Фильтр — [?<выражение>] оставляет только те элементы или члены, для которых выражение истинно (см. ниже).

Скобка может содержать несколько селекторов сразу: [0, 2, "title"] выбирает три вещи в одном сегменте. А сегмент может быть дочерним (одна точка или скобка) или сегментом потомков (..), который ищет в узле и всех его потомках — $..author находит каждый author где угодно в документе.

Фильтры и четыре способа сравнить ничто

Селектор фильтра проверяет каждый элемент логическим выражением, в котором @ ссылается на текущий элемент, а $ — на весь документ. Выражение может сравнивать значения (==, !=, <, <=, >, >=), объединять проверки через && и || и !, и просто проверять существование: $..book[[email protected]] оставляет книги, у которых есть ISBN, потому что @.isbn выбирает узел, только когда ключ присутствует.

Тонкость — в сравнении того, чего нет. Запрос слева или справа от сравнения даёт значение или ничто. RFC определяет это точно: ничто равно ничему, ничто не равно никакому реальному значению, и любая проверка порядка (<, >) против ничто просто ложна. Так что @.price < 10 молча пропускает элемент без price вместо ошибки — что обычно и нужно, и всегда то, что говорит стандарт.

Пять функций

RFC 9535 добавляет пять функций, которые можно вызвать внутри фильтра:

  • length() — длина строки (в символах), массива или объекта.
  • count() — сколько узлов выбирает запрос, чтобы фильтровать по количеству: [?count(@.chapters) > 3].
  • value() — единственное значение, которое выбирает запрос, или ничто, если он выбирает ноль или много.
  • match() — совпадает ли строка с регулярным выражением целиком.
  • search() — находится ли регулярное выражение где-либо в строке.

Стандарт строг к тому, как они используются, и этот тестер обеспечивает это при разборе: length() принимает ровно одно значение, поэтому length(@.*) — запрос, который мог бы выбрать много узлов — это синтаксическая ошибка, а не запрос, который молча ведёт себя неверно. Точно так же match() возвращает истину или ложь, поэтому запись match(@.a, "x") == true отклоняется, потому что логический результат не сравнивают.

Регулярные выражения — это I-Regexp

match() и search() не используют регулярные выражения JavaScript; они используют I-Regexp (RFC 9485), небольшое переносимое подмножество, спроектированное вести себя одинаково во всех языках. Большая его часть — то, что вы ожидаете: классы символов, кванторы, чередование, свойства Unicode вроде \p{Lu} для заглавной буквы. Единственная ловушка — точка: в I-Regexp . совпадает с любым символом, кроме возврата каретки или перевода строки, то есть она всё же совпадает с разделителями строк Unicode U+2028 и U+2029, которые точка JavaScript исключает. Этот тестер компилирует I-Regexp точно, так что шаблон ведёт себя здесь так, как его вычислил бы совместимый сервер.

Разница между двумя функциями — только в привязке: match требует, чтобы совпала вся строка, а search ищет шаблон где-либо в ней. match(@, "a.*") принимает "abc"; search(@, "b") принимает любую строку, содержащую b.

Нормализованные пути и работа в браузере

Для каждого совпадения этот тестер показывает Normalized Path — каноническое местоположение, которое определяет RFC, записанное в форме со скобками и кавычками: $['store']['book'][0]['author']. В отличие от запроса, который может выбрать много узлов, нормализованный путь указывает ровно на один, используя только селекторы имени и индекса и фиксированный стиль кавычек. Это ответ на вопрос «откуда пришло это совпадение», и именно он позволяет превратить результат подстановки обратно в набор конкретных местоположений. Большинство тестеров показывают значения и оставляют вам вычислять пути; этот показывает и то и другое.

Весь движок работает в вашем браузере — JSON разбирается и запрос вычисляется на вашем устройстве, и ничто из вставленного не загружается, не сохраняется и не логируется. Он проверен по официальному набору тестов соответствия JSONPath, так что его ответы согласуются со стандартом, а не с трактовкой одной библиотеки.

Частые вопросы

Какой диалект JSONPath он использует?
RFC 9535, стандарт IETF 2024 года, и он проверен по официальному набору тестов соответствия JSONPath. Он намеренно не принимает старые соглашения Goessner там, где они расходятся с RFC; такой запрос отклоняется с указанием позиции проблемы, чтобы вы могли его исправить.
Почему мой запрос отклонён, хотя он работает в другом инструменте?
Потому что тот инструмент следует до-стандартным соглашениям Goessner, отличающимся от RFC 9535 в нескольких местах — одиночный $.., скриптовые выражения вроде [(@.length-1)], индексы с ведущим нулём и имена без кавычек со специальными символами все нестандартны. RFC заменил их чётко определёнными эквивалентами, и этот тестер придерживается RFC.
Что такое нормализованный путь?
Каноническое местоположение одного узла, записанное в форме со скобками и кавычками, которую определяет RFC, например $['store']['book'][0]['author']. Запрос может совпасть со многими узлами; у каждого совпадения ровно один нормализованный путь, поэтому тестер показывает его рядом с каждым значением.
Как фильтр обходится с отсутствующим значением?
Как с ничем, по правилам, которые фиксирует RFC: ничто равно ничему, ничто не равно никакому реальному значению, и любое сравнение порядка (<, >) с участием ничто ложно. Так что @.price < 10 просто пропускает элемент без price, а не бросает ошибку.
Используют ли match() и search() регулярные выражения JavaScript?
Нет. Они используют I-Regexp (RFC 9485), переносимое подмножество. Главное практическое отличие — точка, которая совпадает со всем, кроме возврата каретки и перевода строки — включая U+2028 и U+2029, которые точка JavaScript исключает. Этот тестер компилирует I-Regexp точно, чтобы результаты соответствовали совместимой реализации.
В чём разница между match и search?
В привязке. match() требует, чтобы вся строка совпала с шаблоном, как будто она заключена в якоря; search() успешен, если шаблон найден где-либо в строке. Всё остальное одинаково.
Отправляется ли мой JSON на сервер?
Нет. Документ разбирается и запрос вычисляется полностью в вашем браузере, и ничто из вставленного не загружается и не логируется. Безопасно тестировать на настоящем ответе API или файле конфигурации.