Тестер JSONPath
Проверка запросов JSONPath по RFC 9535 к JSON: селекторы, фильтры, все пять функций и нормализованный путь каждого совпадения — всё в браузере.
- Путь
$['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 или файле конфигурации.