Описание объекта JavaScript (англ. JavaScript Object Notation, сокращенно JSON) представляет собой распространенный формат обмена данными между различными системами. Так, многие API возвращают результаты именно в этом формате. Поскольку он легко читается и обладает объектной структурой, было бы интересно узнать, как Python работает с данными JSON. В статье мы рассмотрим, в чем суть JSON и как его обрабатывать с помощью встроенного модуля json
в Python.
Структура данных JSON
Данные JSON структурированы как объекты JSON, хранящие данные в виде пар ключ-значение, подобно словарям Python. Так выглядит типичный объект JSON:
{
"firstName": "John",
"lastName": "Smith",
"age": 35,
"city": "San Francisco"
}
По сути, объект JSON заключается в фигурные скобки, внутри которых хранятся пары ключ-значение. Ключи должны быть исключительно строками. Соблюдение данного требования обеспечивает стандартное взаимодействие между различными системами. Представленные значения включают строки и целые числа. При этом JSON также поддерживает и другие типы данных, такие как логическое значение, массивы и объекты.
- Строка (String): строковые литералы, заключенные в двойные кавычки;
- Число (Number): числовые литералы, включая целые и дробные;
- Логическое значение (Boolean):
true
илиfalse
; - Массив (Array): список поддерживаемых типов данных;
- Объект (Object): пары ключ-значение, заключенные в фигурные скобки;
- Null: пустое значение (null) для любого допустимого типа данных.
Следует отметить, что в отличие от строк Python, допускающих одинарные или двойные кавычки, строки JSON заключаются только в двойные. При неправильном применении одинарных кавычек данные JSON становятся недействительными и не подлежат обработке обычным парсером JSON.
Помимо вышеуказанных типов, JSON поддерживает вложенные структуры данных. Например, вы можете вложить объект JSON в другой объект. Кроме того, массив может состоять из любых поддерживаемых типов данных, включая объекты. Приведем примеры:
один объект находится внутри другого:
{
"one": 1,
"two": {"one": 1}
}
массив состоит из нескольких объектов:
[
{"one": 1},
{"two": 2},
{"three": 3}
]
Смешанное использование различных типов данных предоставляет гибкость, позволяющую создавать очень сложные данные с четкой структурной информацией, поскольку все данные сохраняются в виде пар ключ-значение.
Сопоставление типов данных между JSON и Python
Будучи общим форматом обмена данными, типы данных JSON имеют соответствующие нативные структуры данных Python. Обратите внимание на двухсторонний принцип процесса: одно и то же правило (за рядом исключений) действует при преобразовании данных JSON в данные Python и наоборот.
+-----------+----------------+
| JSON | Python |
+-----------+----------------+
| String | str |
| Number | int or float |
| Boolean | bool |
| Array | list |
| Object | dict |
| Null | NoneType |
+-----------+----------------+
Эти преобразования не представляют затруднений, за исключением одного. У Python нет нативного типа данных, соответствующего числам в объектах JSON. Вместо этого для представления целых или вещественных чисел JSON задействуются int
и float
. Как вы могли заметить по таблице, в столбце данных Python отсутствуют кортежи (tuple) и множества (set). Примечательно, что кортеж преобразуется в массив, чего не скажешь о множестве.
Чтение строк JSON
Когда мы читаем и декодируем данные JSON в структуры данных других языков программирования, таких как Python, для дальнейшей обработки, мы говорим, что десериализуем данные JSON. Иными словами, процесс чтения и декодирования называется десериализацией. В стандартную библиотеку Python входит модуль json
, который специализируется на десериализации данных JSON.
Как известно, веб-сервисы обычно используют объекты JSON в качестве ответов API. Допустим, вы получаете следующий ответ, который для простоты понимания представим в виде строкового объекта Python:
employee_json_data = """{
"employee0": {
"firstName": "John",
"lastName": "Smith",
"age": 35,
"city": "San Francisco"
},
"employee1": {
"firstName": "Zoe",
"lastName": "Thompson",
"age": 32,
"city": "Los Angeles"
}
}"""
Прочитаем эту строку JSON с помощью метода loads
. Как показано ниже, после прочтения строки, содержащей вышеуказанный объект JSON, мы можем получить объект dict
:
import json
employee_data = json.loads(employee_json_data)
print(employee_data)
# {'employee0': {'firstName': 'John', 'lastName': 'Smith', 'age': 35, 'city': 'San Francisco'}, 'employee1': {'firstName': 'Zoe', 'lastName': 'Thompson', 'age': 32, 'city': 'Los Angeles'}}
Отметим гибкость метода loads
. При наличии строки, представляющей список объектов JSON, этот метод самостоятельно определяет, как надлежит парсить данные. Обратимся к примеру:
employee_json_array = '[{"employee2": "data"}, {"employee3": "data"}]'
employee_list = json.loads(employee_json_array)
print(employee_list)
# [{'employee2': 'data'}, {'employee3': 'data'}]
Помимо этих структурированных объектов JSON, метод loads
способен парсить любые отличные от объектов типы данных JSON. Приведем примеры:
>>> json.loads("2.2")
2.2
>>> json.loads('"A string"')
'A string'
>>> json.loads('false')
False
>>> json.loads('null') is None
True
Чтение файлов JSON
В предыдущем разделе были затронуты различные аспекты, касающиеся десериализации строк JSON. Однако взаимодействовать приходится не только со строками. Иногда выпадает возможность поработать и с файлами JSON. Допустим, вы выполняете следующий код для создания файла, содержащего строки JSON:
# данные JSON,которые нужно сохранить
json_to_write='{"name": "John", "age": 35}'
# запись данных JSON в файл
with open("json_test.txt", "w") as file:
file.write(json_to_write)
Конечно же, мы можем прочитать файл напрямую, чтобы создать строку и отправить ее в метод loads
:
with open(“json_test.txt”) as file:
json_string = file.read()
parsed_json0 = json.loads(json_string)
print(parsed_json0)
# Вывод: {'name': 'John', 'age': 35}
Примечательно, что модуль json
предоставляет метод load
, позволяющий работать напрямую с файлом для парсинга данных JSON:
with open(“json_test.txt”) as file:
parsed_json1 = json.load(file)
print(parsed_json1)
# Вывод: {‘name’: ‘John’, ‘age’: 35}
Данный способ несомненно более понятный, чем предыдущая реализация, поскольку он избавляет от необходимости создавать промежуточный строковый объект.
Итак, мы рассмотрели самые основные сценарии использования методов load
и loads
. Следует отметить, что парсинг данных JSON осуществляется посредством класса JSONDecoder
. Несмотря на эффективность этого базового класса в решении большинства задач, мы можем определить более настраиваемое поведение путем создания подкласса класса JSONDecoder
. Однако если вы намерены обойтись без подклассов, то методы load
и loads
предоставят другие параметры, с помощью которых вы сможете определить настраиваемое поведение парсинга. Удовлетворить любопытство и ознакомиться с дополнительной информацией можно в официальной документации.
Запись данных Python в формат JSON
По аналогии с чтением данных JSON запись данных Python в формат JSON включает два соответствующих метода, а именно dump
и dumps
. В противоположность десериализации процесс создания данных JSON называется сериализацией. Таким образом, когда мы преобразуем данные Python в данные JSON, мы говорим, что сериализуем объекты Python в данные JSON.
Подобно load
и loads
методы dump
и dumps
имеют почти идентичные сигнатуры вызовов. Главное отличие состоит в том, что метод dump
записывает данные в файл JSON, тогда как dumps
— в строку JSON. Для простоты остановимся только на методе dumps
. Рассмотрим пример:
import json
different_data = ['text', False, {"0": None, 1: [1.0, 2.0]}]
json.dumps(different_data)
# Вывод: '["text", false, {"0": null, "1": [1.0, 2.0]}]'
Как видно, метод dumps
создает массив JSON, содержащий различные типы данных JSON. Особое внимание обращает на себя следующий факт: хотя исходный объект list
использует нативные структуры данных Python, сгенерированная строка JSON содержит преобразованные структуры данных JSON. В соответствии с ранее изученной таблицей отметим следующие преобразования:
- Строка
‘text’
в одинарных кавычках теперь заключена в двойные —“text”
. - Логический объект Python
False
становитсяfalse
. - Объект
None
превращается вnull
. - Поскольку ключами JSON могут быть только строки, число
1
автоматически преобразуется в его строковый аналог“1”
.
Помимо автоматических преобразований мы часто задействуем две важные функциональности. Первая предназначена для создания объектов JSON в более читаемом формате посредством правильной установки отступов. Для этого в методе dumps
задается параметр indent
:
employee_data = [{"name": "John", "age": 35, "city": "San Francisco"}, {"name": "Zoe", "age": 34, "city": "Los Angeles"}]
print(json.dumps(employee_data, indent=2))
# Вывод:
[
{
"name": "John",
"age": 35,
"city": "San Francisco"
},
{
"name": "Zoe",
"age": 34,
"city": "Los Angeles"
}
]
Как показано выше, каждый уровень четко оформлен при помощи отступов, чтобы обозначить взаимосвязанную структуру объектов JSON и их пар ключ-значение.
Вторая полезная функциональность — указание параметра sort_keys
. Установив его в значение True
, мы создаем строки JSON, ключи которых отсортированы в алфавитном порядке. Этот прием упрощает поиск информации, особенно при наличии нескольких элементов. Обратимся к примеру:
employee_info = {"name": "John", "age": 35, "city": "San Francisco", "home": "123 Main St.", "zip_code": 12345, "sex": "Male"}
print(json.dumps(employee_info, indent=2, sort_keys=True))
# output:
{
"age": 35,
"city": "San Francisco",
"home": "123 Main St.",
"name": "John",
"sex": "Male",
"zip_code": 12345
}
Теперь мы знаем, что методы load
и loads
применяются для десериализации, а dump
и dumps
— для сериализации. Во избежание недопонимания поясним, почему методы получили именно такие названия:
- Данные JSON являются внешними по отношению к Python. При необходимости получить к ним доступ нужно “загрузить” (“load”) их в Python. Следовательно, загрузка (loading) подразумевает чтение данных JSON.
- И наоборот, для экспорта данных Python в данные JSON мы “разгружаем” (“dump”) данные. Следовательно, под разгрузкой (dumping) имеется в виду запись данных JSON.
- Если входные или выходные данные JSON являются строками, то они обозначаются буквой “s”. Поэтому мы присоединяем “s” к методу
load
. Таким же образом, если нам необходимы строки JSON, мы добавляем “s” к названию методаdump
.
Запись пользовательских экземпляров в данные JSON
Под прицелом нашего внимания — встроенные структуры данных Python. Во многих приложениях вы определяете собственные пользовательские классы при необходимости сериализации этих пользовательских объектов экземпляров в данные JSON. Рассмотрим следующий класс, из которого создадим экземпляр:
class Employee:
def __init__(self, name, employee_id):
self.name = name
self.employee_id = employee_id
employee = Employee("John Smith", 40)
Что произойдет, если мы попробуем вызвать dumps
для employee
? Получим ли мы успешный результат? Проверяем:
json.dumps(employee)
# TypeError: Object of type Employee is not JSON serializable
Не сработало. Причина неудачи в том, что метод dumps
пытается создать корректную строку JSON. Однако в случае с экземпляром пользовательского класса он не знает, какие данные подлежат кодированию. Один из вариантов решения — создать собственный класс JSONEncoder
. Однако есть более быстрый способ: мы предоставляем методу dumps
инструкции по кодированию, устанавливая аргумент default
:
>>> json.dumps(employee, default=lambda x: x.__dict__)
'{"name": "John Smith", "employee_id": 40}'
Здесь указывается лямбда-функцию, которая извлекает представление dict
экземпляра через доступ к специальному атрибуту __dict__
. Нам известно, что встроенный объект dict
сериализуется в JSON, поэтому dumps
“разгружает” объект dict
.
Заключение
В статье были рассмотрены основные способы обработки данных JSON в Python. Сформулируем главные выводы:
- Данные JSON — это стандартный формат обмена данными. При создании API для всеобщего пользования JSON рекомендуется в качестве возможного формата для данных ответа.
- У Python есть отдельные методы для работы со строками и файлами JSON. Эти методы обладают похожими сигнатурами вызовов.
- Правильно устанавливайте отступы для улучшения читаемости данных JSON, особенно при создании соответствующей строки. Просто укажите параметр
indent
при сериализации объектов - При наличии нескольких пар ключ-значение для объектов JSON рекомендуется сортировать ключи, тем самым упрощая поиск информации.
- Ключи JSON должны быть строками в двойных кавычках.
- Для сериализации пользовательского экземпляра необходимо предоставить конкретные инструкции.
Читайте также:
- Прекратите использовать конфигурационные файлы JSON
- 4 бесплатные игры для изучения Python
- Работа с панелью индикаторов. Руководство программиста Python. Часть 3
Читайте нас в Telegram, VK и Дзен
Перевод статьи Yong Cui: How to Make JSON and Python Talk to Each Other