Руководство по запросам JSONPath: как искать данные в JSON
JSON стал универсальным языком веба. API возвращают JSON, конфигурационные файлы используют JSON, базы данных хранят JSON-документы. Но по мере того, как JSON-структуры становятся больше и более вложенными, поиск конкретных значений становится всё сложнее. Здесь на помощь приходит JSONPath. JSONPath — это язык запросов, позволяющий навигировать и извлекать данные из JSON-документов с помощью лаконичных выражений пути, подобно тому, как XPath делает это для XML. Это руководство охватывает всё от базового синтаксиса до продвинутой фильтрации с практическими примерами, которые вы можете применить немедленно.
Что такое JSONPath?
JSONPath — это язык запросов JSON, первоначально предложенный Stefan Goessner в 2007 году. Он предоставляет компактный синтаксис для выбора узлов из JSON-документов, подобно тому, как CSS-селекторы нацеливаются на HTML-элементы или выражения XPath нацеливаются на XML-узлы. Вместо написания циклов и условной логики для обхода JSON-структур вы просто пишете выражение, описывающее путь к нужным данным.
Выражения JSONPath начинаются с корневого узла JSON-документа и навигируют через объекты и массивы к нужным значениям. Язык поддерживает подстановочные знаки, рекурсивный спуск, срезы массивов и выражения фильтрации, что достаточно для большинства задач извлечения данных.
Пример JSON-документа
В этом руководстве мы будем использовать следующий JSON-документ в качестве рабочего примера. Это классический пример из оригинального предложения JSONPath с дополнительными данными:
{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J.R.R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}Справочник по синтаксису JSONPath
JSONPath использует небольшой набор операторов, которые комбинируются для формирования мощных запросов. Вот полный справочник синтаксиса:
| Оператор | Описание | Пример |
|---|---|---|
| $ | Корневой узел документа | $ |
| . | Оператор потомка (доступ к свойствам объекта) | $.store |
| [] | Оператор индекса массива или потомка | $.store.book[0] |
| [*] | Подстановочный знак для всех элементов массива | $.store.book[*] |
| .. | Рекурсивный спуск (поиск на всех уровнях) | $..author |
| .key | Именованное свойство объекта | $.store.bicycle |
| ['key'] | Скобочная нотация для доступа к свойствам | $['store']['book'] |
| [start:end] | Срез массива (конечный индекс не включается) | $.store.book[0:2] |
| [?()] | Выражение фильтрации | < 10)] |
| () | Скриптовое выражение (зависит от реализации) | $.store.book[(@.length-1)] |
Базовые выражения JSONPath
Доступ к корневому узлу и прямым потомкам
Знак доллара $ представляет корневой узел JSON-документа. Оттуда вы используете точечную нотацию для доступа к свойствам объекта и скобочную нотацию для доступа к индексам массива.
| Выражение | Результат |
|---|---|
| $ | Весь JSON-документ |
| $.store | Объект store (содержит массив book и объект bicycle) |
| $.store.bicycle | {"color": "red", "price": 19.95} |
| $.store.bicycle.color | "red" |
| $.store.book | Весь массив book |
| $.store.book[0] | Первый объект книги |
| $.store.book[0].title | "Sayings of the Century" |
| $.store.book[3].author | "J.R.R. Tolkien" |
Скобочная нотация
Скобочная нотация является альтернативой точечной нотации и полезна, когда имена свойств содержат специальные символы, пробелы или начинаются с цифры:
$.store['book'][0]['title']
$['store']['bicycle']['color']Скобочная и точечная нотации взаимозаменяемы для свойств объектов. Однако скобочная нотация обязательна, когда имя свойства динамическое или содержит символы, недопустимые в точечной нотации.
Оператор подстановки
Оператор подстановки * соответствует всем элементам массива или всем свойствам объекта. Он чрезвычайно полезен для извлечения всех значений на определённом уровне без знания конкретных ключей или индексов.
| Выражение | Результат |
|---|---|
| $.store.* | Все значения в объекте store (массив book и объект bicycle) |
| $.store.book[*] | Все книги в массиве (все четыре объекта book) |
| $.store.book[*].author | Все имена авторов: ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J.R.R. Tolkien"] |
| $.store.*.price | Цена bicycle и массив book (не цены отдельных книг) |
Рекурсивный спуск: оператор двойной точки
Оператор двойной точки .. — одна из самых мощных возможностей JSONPath. Он ищет указанный ключ на каждом уровне JSON-дерева, а не только среди прямых потомков. Думайте о нём как о глубоком поиске по всему документу.
| Выражение | Результат |
|---|---|
| $..author | Значения author во всех местах документа |
| $..price | Все значения price: [8.95, 12.99, 8.99, 22.99, 19.95] |
| $..book[2] | Третья книга, найденная рекурсивно |
| $..category | Все значения category: ["reference", "fiction", "fiction", "fiction"] |
Оператор рекурсивного спуска особенно полезен, когда вы не знаете точный путь к нужным данным или когда один и тот же ключ появляется на нескольких уровнях вложенности. Например, если ответ API содержит поля id на разных уровнях вложенности, $..id соберёт все id.
Срезы массивов
Срезы массивов позволяют выбрать диапазон элементов из массива. Синтаксис [start:end] выбирает элементы от начального индекса до (но не включая) конечного индекса. Поддерживаются как положительные, так и отрицательные индексы.
| Выражение | Результат |
|---|---|
| $.store.book[0:2] | Первые две книги (индексы 0 и 1) |
| $.store.book[1:3] | Вторая и третья книги (индексы 1 и 2) |
| $.store.book[-1] | Последняя книга (The Lord of the Rings) |
| $.store.book[-2:] | Последние две книги |
| $.store.book[:2] | Первые две книги (то же, что [0:2]) |
| $.store.book[2:] | Третья книга и далее |
Обратите внимание, что поведение срезов может немного отличаться в разных реализациях JSONPath. Приведённый выше синтаксис следует соглашениям, используемым большинством популярных библиотек и стандартом IETF RFC 9535.
Выражения фильтрации
Выражения фильтрации — это то, где JSONPath становится по-настоящему мощным. Они позволяют выбирать элементы на основе условий, а не позиции. Синтаксис фильтрации использует [?(condition)], где условие оценивается для каждого элемента.
Операторы сравнения
JSONPath поддерживает следующие операторы сравнения в выражениях фильтрации:
| Оператор | Значение | Пример |
|---|---|---|
| == | Равно | [?(@.category == "fiction")] |
| != | Не равно | [?(@.category != "fiction")] |
| < | Меньше | < 10)] |
| <= | Меньше или равно | <= 8.99)] |
| > | Больше | [?(@.price > 15)] |
| >= | Больше или равно | [?(@.price >= 12.99)] |
| =~ | Соответствие регулярному выражению (частичная поддержка) | [?(@.author =~ /Tolkien/i)] |
Практические примеры фильтрации
Используя наш пример документа, вот практические выражения фильтрации и их результаты:
Find all books cheaper than $10:
< 10)]Возвращает книги "Sayings of the Century" ($8.95) и "Moby Dick" ($8.99).
Find all fiction books:
$.store.book[?(@.category == "fiction")]Возвращает три книги: "Sword of Honour", "Moby Dick" и "The Lord of the Rings".
Find books with an ISBN:
$.store.book[?(@.isbn)]Возвращает книги, имеющие свойство isbn: "Moby Dick" и "The Lord of the Rings". Это работает, потому что фильтр проверяет существование свойства.
Find the most expensive book:
$.store.book[?(@.price > 20)]Возвращает "The Lord of the Rings" ($22.99).
Логические операторы в фильтрах
Вы можете комбинировать условия с помощью логических операторов:
< 10 && @.category == "fiction")]
$.store.book[?(@.price > 15 || @.category == "reference")]Первое выражение находит художественные книги дешевле $10 (только "Moby Dick"). Второе находит книги дороже $15 или в категории reference (возвращает "Sayings of the Century" и "The Lord of the Rings").
JSONPath и XPath
JSONPath был явно смоделирован по образцу XPath, и эти два языка имеют много концептуальных сходств. Понимание этой связи полезно, если вы знакомы с обработкой XML.
| Функция | JSONPath | XPath |
|---|---|---|
| Корневой символ | $ | / |
| Доступ к потомкам | .key или ['key'] | /element |
| Индекс массива | [0] | [1] (начинается с 1) |
| Подстановочный знак | * | * |
| Рекурсивный спуск | .. | // |
| Фильтрация | [?(condition)] | [condition] |
| Атрибут | Н/Д (JSON не имеет атрибутов) | @attr |
| Текущий узел | @ (в фильтрах) | . или current() |
| Родитель | Не поддерживается | .. |
| Оси | Не поддерживаются | 13 осей (ancestor, following и т.д.) |
| Модель данных | Объекты и массивы | Элементы, атрибуты, текстовые узлы |
Ключевое отличие в том, что XPath работает с богатой древовидной моделью, имеющей элементы, атрибуты, текстовые узлы, пространства имён и инструкции обработки. JSONPath работает с более простой моделью объектов (отображений ключ-значение) и массивов (упорядоченных списков). Эта простота делает JSONPath легче в изучении, но менее выразительным, чем XPath для сложных запросов.
JSONPath на практике
Тестирование API
JSONPath незаменим для тестирования API. Когда вы отправляете запрос к API и получаете большой JSON-ответ, JSONPath позволяет вам утверждать конкретные значения без ручной навигации по всей структуре. Большинство инструментов тестирования API нативно поддерживают JSONPath.
Например, в тесте вы можете проверить, что цена первой книги меньше $10:
// Using a JSONPath assertion in testing
response.jsonPath().get("store.book[0].price").should(equals(8.95));
// Find all books by a specific author
response.jsonPath().get("store.book[?(@.author == 'Herman Melville')].title");
// Returns: ["Moby Dick"]Трансформация данных
При интеграции систем, использующих разные форматы данных, JSONPath помогает извлекать и преобразовывать конкретные поля. Вы можете извлечь значения из одной JSON-структуры и сопоставить их с другой без написания сложного кода обхода.
Управление конфигурацией
Сложные конфигурационные файлы часто содержат глубоко вложенный JSON. JSONPath позволяет запрашивать конкретные значения конфигурации без загрузки и парсинга всей структуры. Инструменты, такие как jq, используют синтаксис, подобный JSONPath, для обработки JSON в конвейерах командной строки.
Мониторинг и оповещения
В системах наблюдаемости запросы JSONPath могут извлекать метрики из JSON-форматированных логов и ответов API. Вы можете настроить оповещения, которые срабатывают, когда запрос JSONPath возвращает значение, превышающее порог.
Реализации JSONPath
JSONPath доступен практически на всех языках программирования. Вот самые популярные библиотеки:
| Язык | Библиотека | Установка |
|---|---|---|
| JavaScript | jsonpath-plus | npm install jsonpath-plus |
| Python | jsonpath-ng | pip install jsonpath-ng |
| Java | JsonPath (Jayway) | Maven: com.jayway.jsonpath |
| C# | Json.NET (Newtonsoft) | NuGet: Newtonsoft.Json |
| Go | gjson | go get github.com/tidwall/gjson |
| PHP | jsonpath | composer require softcreatr/jsonpath |
| Ruby | jsonpath | gem install jsonpath |
Пример на JavaScript
< 10)]',
json: data
});
// Returns books with price < 10
Python Example
from jsonpath_ng import parse
data = { /* our sample JSON */ }
# Find all authors
author_expr = parse('$..author')
authors = [match.value for match in author_expr.find(data)]
# ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J.R.R. Tolkien"]
# Find cheap books
cheap_expr = parse('$.store.book[?(@.price < 10)]')
cheap_books = [match.value for match in cheap_expr.find(data)]
Common Pitfalls and Tips
- Zero-based vs one-based indexing: JSONPath uses zero-based indexing (the first element is [0]), while XPath uses one-based indexing (the first element is [1]). This is a common source of off-by-one errors.
- Implementation differences: Before the IETF standard (RFC 9535), JSONPath implementations varied in their handling of edge cases like empty results, null values, and filter syntax. Always test your expressions with the specific library you are using.
- Filter performance: Recursive descent with filters (
$..book[?(@.price < 10)]) can be slow on large documents because it must traverse the entire tree. For performance-critical applications, use more specific paths when possible. - Case sensitivity: JSONPath is case-sensitive.
$.Storewill not match$.store. This is consistent with JSON's case-sensitive nature. - Индексация с нуля и с единицы: JSONPath использует индексацию с нуля (первый элемент — [0]), в то время как XPath использует индексацию с единицы (первый элемент — [1]). Это распространённый источник ошибок off-by-one.
- Различия в реализациях: до стандарта IETF (RFC 9535) реализации JSONPath различались в обработке граничных случаев, таких как пустые результаты, значения null и синтаксис фильтрации. Всегда тестируйте выражения с вашей конкретной библиотекой.
Распространённые pitfalls и советы
В 2024 году IETF опубликовал RFC 9535, формально стандартизировав JSONPath. Спецификация устраняет многие неоднозначности и несоответствия, существовавшие между реализациями. Ключевые аспекты стандарта включают:
- < 10)]) может быть медленным на больших документах, поскольку должен обходить всё дерево. Для критичных к производительности приложений используйте более конкретные пути, когда это возможно.
- Чувствительность к регистру: JSONPath чувствителен к регистру. $.Store не будет соответствовать $.store. Это согласуется с чувствительной к регистру природой JSON.
- Экранирование специальных символов: если имена ключей содержат точки или скобки, вы должны использовать скобочную нотацию с кавычками: $['key.with.dots'] вместо $.key.with.dots.
- Отсутствие обхода к родителю: в отличие от XPath, JSONPath не может навигировать к родительскому узлу. Нет эквивалента .. (родительской оси) XPath. .. в JSONPath означает рекурсивный спуск, а не родителя.
Если вы начинаете новый проект, отдавайте предпочтение библиотекам, реализующим RFC 9535, для максимальной совместимости и предсказуемого поведения.
Нужно быстро выполнить запрос к JSON-данным? Попробуйте наш бесплатный онлайн-тестер JSONPath, который оценивает выражения в реальном времени на вашем JSON-документе.
Попробовать JSONPath FinderСтандарт IETF: RFC 9535
Пример на Python
JSONPath — это язык запросов JSON, подобно тому, как XPath для XML. Он использует выражения пути для навигации и извлечения конкретных значений из JSON-документов. Выражение JSONPath, такое как $.store.book[0].title, позволяет точно находить данные в сложных вложенных JSON-структурах без написания пользовательского кода парсинга.
Что такое JSONPath?
JSONPath разработан для объектно-массивной структуры JSON, в то время как XPath разработан для древовидной структуры элементов и атрибутов XML. JSONPath использует $ как корневой узел, точечную нотацию для доступа к объектам и скобочную нотацию для доступа к массивам. XPath использует / для разделения пути и @ для доступа к атрибутам. JSONPath проще, но менее функционально богат, чем XPath.
Чем JSONPath отличается от XPath?
Двойная точка (..) в JSONPath — это оператор рекурсивного спуска. Он ищет именованный ключ на всех уровнях JSON-структуры, а не только среди прямых потомков. Например, $..author находит все ключи author в любом месте документа, независимо от глубины вложенности.
Что означает двойная точка (..) в JSONPath?
<, ><=, >< 10)] находит все книги дешевле $10.
Может ли JSONPath фильтровать данные по условию?
JSONPath был первоначально предложен Stefan Goessner в 2007 году без формальной спецификации, что привело к различиям между реализациями. В 2024 году IETF опубликовал RFC 9535, формально стандартизировав JSONPath. Современные реализации сходятся к этому стандарту, но некоторые старые библиотеки могут иметь небольшие синтаксические различия.