Работа с JSONPath в Python: руководство по освоению

Хотите узнать, как использовать JSONPath в Python для извлечения определенных данных из ваших документов JSON? Вы находитесь в правильном месте.

JSONPath — это язык запросов, который может извлекать данные из документов JSON (например, строки JSON или файла JSON). Одной из основных реализаций JSONPath для Python является модуль jsonpath-ng. Этот модуль понимает синтаксис JSONPath и возвращает часть документа, которую вы хотите выбрать, с помощью выражения JSONPath.

Мы рассмотрим несколько примеров, начиная с самого простого, чтобы вы могли привыкнуть к синтаксису модуля jsonpath-ng.

Как только вы освоите этот модуль, вам будет легче понимать более сложные выражения синтаксического анализа.

Что такое JSONPath?

Вы когда-нибудь задумывались, как извлечь данные из документа JSON?

Один из способов — JSONPath…

JSON Path — это язык запросов, позволяющий извлекать определенные данные из документа JSON аналогично XPath для XML.

Альтернативой JSONPath является программный просмотр структуры данных, возвращаемой модулем Python json, однако использование этого подхода может оказаться менее эффективным с точки зрения использования памяти по сравнению с использованием JSONPath.

Во время изучения этого руководства вы можете протестировать выражения JSONPath в своем браузере с помощью этого онлайн-инструмента.

Какой модуль можно использовать для оценки JSONPath в строке JSON в Python?

Для оценки JSONPath в строке JSON с помощью Python можно использовать модуль jsonpath-ng.

То же самое относится к данным JSON, извлеченным из файла.

Как установить модуль jsonpath-ng

Для установки модуля jsonpath-ng вы можете использовать следующую команду PIP:

pip3 install jsonpath-ng

Примечание: если у вас локально не установлен модуль jsonpath-ng, при попытке импортировать этот модуль вы увидите следующую ошибку.

ModuleNotFoundError: No module named 'jsonpath_ng'

Как получить значение атрибута с помощью Python JSONPath

Давайте возьмем простой JSON-файл с именем cities.json, содержащий один JSON-объект.

{
    "city": "Paris",
    "country": "France"
}

Прежде всего, используйте модуль json для извлечения содержимого файла.

import json

with open("cities.json", "r") as jsonfile:
    json_data = json.load(jsonfile)

print(type(json_data))
print(json_data)

Как вы можете видеть ниже, переменная json_data является словарем и содержит JSON, считанный из файла.

$ python jsonpath_example.py
<class 'dict'>
{'city': 'Paris', 'country': 'France'}

Следующим шагом является определение правила, позволяющего извлекать значение атрибута из данных JSON, например значение атрибута «город».

Для этого сначала определим выражение с помощью jsonpath-ng…

import json, jsonpath_ng

with open("cities.json", "r") as json_file:
    json_data = json.load(json_file)

jsonpath_expr = jsonpath_ng.parse("$.city")

Мы использовали символ доллара в начале выражения, переданного в jsonpath_ng.parse().

Как работает знак доллара с jsonpath-ng?

При написании выражения синтаксического анализа JSONPath на Python знак доллара представляет корневой объект (полный объект для нашего документа JSON).

Следующий шаг — использовать это выражение для поиска нужных нам данных в JSON.

Мы можем использовать следующую строку кода:

extracted_data = jsonpath_expr.find(json_data)

Мы используем метод find объекта jsonpath_expr.

Давайте узнаем больше о переменной extraction_data, возвращаемой методом find с использованием функции print Python.

print(f"The variable extracted_data is of type {type(extracted_data)} and it has {len(extracted_data)} elements.")
print(f"The value of extracted_data is {extracted_data}")

Примечание: в этих двух операторах печати мы используем f-строки.

Выходные данные…

The variable extracted_data is of type <class 'list'> and it has 1 elements.
The value of extracted_data is [DatumInContext(value='Paris', path=Fields('city'), context=DatumInContext(value={'city': 'Paris', 'country': 'France'}, path=Root(), context=None))]

Интересный…

Мы узнали кое-что новое: переменная, возвращаемая функцией find (extract_data), представляет собой список Python, содержащий один элемент.

Значение этого элемента можно увидеть в выводе второго оператора печати.

Но как нам получить значение атрибута city?

Мы делаем это, обращаясь к атрибуту value элемента списка (доступ осуществляется с использованием индекса 0, учитывая, что это единственный элемент в списке).

print(f"The city is {extracted_data[0].value}")

[output]
The city is Paris

Еще один пример получения значения атрибута JSON с помощью JSONPath

Чтобы поближе познакомиться с jsonpath-ng, давайте обновим содержимое нашего JSON-файла, как показано ниже.

{
    "city": "Paris",
    "country": {
        "name": "France",
        "identifier": "FR"
    }
}

На этот раз значение атрибута страны — не строка, а объект JSON.

Давайте посмотрим, что произойдет, когда мы попытаемся получить значение атрибута country.

jsonpath_expr = jsonpath_ng.parse("$.country")
extracted_data = jsonpath_expr.find(json_data)
print(f"The data is {extracted_data[0].value}")

Примечание: остальная часть кода остается прежней.

[output]
The data is {'name': 'France', 'identifier': 'FR'}

А теперь давайте посмотрим, сможем ли мы получить идентификатор, просто снова используя точечную нотацию в выражении, которое мы передали в jsonpath_ng.parse().

Код Python становится…

jsonpath_expr = jsonpath_ng.parse("$.country.identifier")
extracted_data = jsonpath_expr.find(json_data)
print(f"The data is {extracted_data[0].value}")

И на выходе получается…

The data is FR

Это хорошо, у нас есть базовое понимание того, как извлекать атрибуты.

Давайте рассмотрим что-то более сложное…

Как проанализировать массив JSON в Python с помощью JSONPath

Обновите файл JSON, над которым мы работаем, чтобы включить в него несколько городов вместо одного.

Другими словами, JSON-файл будет содержать JSON-массив.

Вот как файл становится…

{
    "cities": [
        {
            "city": "Paris",
            "country": {
                "name": "France",
                "identifier": "FR"
            }
        },
        {
            "city": "London",
            "country": {
                "name": "United Kingdom",
                "identifier": "UK"
            }
        },
        {
            "city": "New York",
            "country": {
                "name": "United States",
                "identifier": "US"
            }
        }
    ]
}

Допустим, мы хотим получить атрибут идентификатора для каждого элемента в массиве JSON.

Как мы можем это сделать?

Давайте откроем оболочку Python и попробуем несколько вещей…

>>> import json, jsonpath_ng
>>> with open("cities.json", "r") as json_file:
...     json_data = json.load(json_file)
... 
>>> jsonpath_expr = jsonpath_ng.parse("$.cities.city")
>>> extracted_data = jsonpath_expr.find(json_data)
>>> extracted_data
[]

Это не работает, мы получили пустой массив.

Давайте посмотрим, сможем ли мы передать индекс массиву городов в выражении синтаксического анализа.

>>> jsonpath_expr = jsonpath_ng.parse("$.cities[0].city")
>>> extracted_data = jsonpath_expr.find(json_data)  
>>> extracted_data[0].value
'Paris'

Оно работает!

Итак, как можно извлечь значение одного и того же атрибута из каждого объекта JSON в массиве JSON?

Чтобы сослаться на все элементы массива JSON с помощью JSONPath в Python, можно использовать [*] рядом с именем массива JSON.

Наш код становится…

>>> jsonpath_expr = jsonpath_ng.parse("$.cities[*].city")
>>> extracted_data = jsonpath_expr.find(json_data)
>>> extracted_data[0].value
'Paris'
>>> extracted_data[1].value
'London'
>>> extracted_data[2].value
'New York'
>>> extracted_data[3].value
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

Мы получаем исключение выхода индекса списка за пределы диапазона при доступе к четвертому элементу списка extraction_data , поскольку этот список содержит только три элемента — три города в файле JSON.

Мы также можем использовать цикл for для вывода городов, извлеченных с помощью выражения JSONPath:

>>> for match in extracted_data:
...     print(match.value)
... 
Paris
London
New York

Имеет ли это смысл?

Еще один пример выражения JSONPath на Python

Давайте оставим наш JSON-файл без изменений…

Я хочу показать вам еще кое-что, что можно сделать с помощью JSONPath.

Можно ли извлечь данные из внутреннего объекта JSON, не указывая каждый отдельный узел документа JSON в выражении анализа?

Ответ — да, и мы можем сделать это, используя следующий синтаксис:

jsonpath1..jsonpath2

Это выражение позволяет извлечь все узлы, соответствующие jsonpath2, происходящие от любого узла, соответствующего jsonpath1.

Таким образом, в нашем сценарии мы можем извлечь атрибут «country» без необходимости указывать JSON-массив «citys» в выражении анализа.

Вот как…

import json, jsonpath_ng

with open("cities.json", "r") as json_file:
    json_data = json.load(json_file)

jsonpath_expr = jsonpath_ng.parse("$..country")
extracted_data = jsonpath_expr.find(json_data)

for match in extracted_data:
    print(f"The country data is {match.value}")

Если вы выполните этот код, вы получите следующий вывод:

The country data is {'name': 'France', 'identifier': 'FR'}
The country data is {'name': 'United Kingdom', 'identifier': 'UK'}
The country data is {'name': 'United States', 'identifier': 'US'}

В чем разница между JSON и JSONPath?

JSON расшифровывается как JavaScript Object Notation и представляет собой формат для хранения и обмена данными между системами или приложениями.

Модуль json — наиболее распространённый модуль Python для чтения и записи данных JSON.

JSONPath, с другой стороны, позволяет извлекать данные из документа JSON без необходимости просматривать структуру данных, возвращаемую модулем json при чтении строки JSON или файла JSON.

Заключение

Надеюсь, это руководство по JSONPath оказалось для вас полезным, и рассмотренные мной примеры дали вам достаточно знаний для продолжения тестирования выражений JSONPath в составе вашего кода Python.

Кроме того, не волнуйтесь, если поначалу вам покажется сложным разобраться с JSONPath, это совершенно нормально.

Вы нашли этот урок полезным? Хотите вывести свои навыки Python на новый уровень?

Автор

Фото аватара

Владимир Михайлов

Программист на Python с большим количеством опыта и разнообразных проектов.