Из этой статьи вы узнаете, что такое текстовый формат JSON и для чего он был придуман. Мы рассмотрим парочку простых нотаций в этом формате и несколько несложных примеров работы с JSON на языке программирования Python. А также научимся добавлять, изменять и удалять данные в JSON и еще кое-что интересное.
Что такое JSON и зачем он нужен
JSON (JavaScript Object Notation) — это текстовый формат обмена данными, основанный на синтаксисе объектов JavaScript. Он представляет информацию в виде структурированных пар «ключ‑значение», позволяя описывать сложные иерархические структуры — объекты, массивы, вложенные элементы. Формат является платформонезависимым: он может использоваться в любой операционной системе и его могут читать и обрабатывать программы на любых языках программирования.
Основная задача JSON — обеспечивать удобный и компактный способ передачи данных между различными системами и компонентами приложений. Он широко применяется в веб‑разработке: при обмене информацией между клиентскими приложениями и серверами через API, хранении конфигурационных настроек, передаче данных в мобильных приложениях. Благодаря простоте синтаксиса и читаемости JSON стал одним из самых популярных форматов для интеграции разнородных систем.
Примеры нотаций на JSON
Ниже показан пример нотации, который наглядно демонстрирует всю простоту формата JSON. В примере указаны данные для некоторого вымышленного персонажа по имени Андрей:
{
"firstName": "Andrey",
"lastName": "Ivanov",
"hobbies": ["programming", "reading", "drawing"],
"age": 42,
"children":
{
"son": {
"name": "Victor",
"age": 7
},
"daughter": {
"name": "Olga",
"age": 12
}
}
}
Мы видим набор ключей и их значений, которые составляют основу нотаций на JSON. Такие наборы являются неупорядоченными коллекциями и называются объектами. Ключи всегда являются строками и заключаются в кавычки. Значениями ключей могут быть строки, числа, логические значения, списки или другие объекты. Объекты заключаются в фигурные скобки, а списки — в квадратные.
Как видно из представленной выше нотации, наш Андрей носит фамилию Иванов и увлекается программированием, чтением и рисованием. Также видим, что возраст у нашего персонажа 42 года, а еще у него есть двое детей — сын Виктор семи лет и дочь Ольга 12-и лет. Или вот еще пример подобной нотации:
{
"planetName": "Mars",
"group": "earth group",
"radius": 3389.5,
"satellites": [
{
"name": "Fobos"
},
{
"name": "Deimos"
}
]
}
Эта нотация составлена для планеты Марс. Приводится группа, к которой принадлежит планета, ее радиус и спутники. Как известно, у Марса всего два спутника: Фобос и Деймос.
Загрузка и получение данных
В языке Python имеется модуль для работы с данными в формате JSON. Импортируем его:
import json
И теперь мы можем пользоваться всеми функциями из этого модуля. Допустим, у нас имеется файл data.json. Чтобы загрузить данные из этого файла можно использовать функции open с параметром r (для чтения) и load:
with open('data.json', 'r') as file:
data = json.load(file)
А если данные хранятся в строковом виде то в этом случае следует использовать функцию loads:
json_string = '{"name": "Andrey", "age": 42}'
data = json.loads(json_string)
Эти функции преобразуют данные в словари и списки, что позволяет достаточно просто получать к ним доступ. Более подробно про списки можно прочитать в этой статье, а про словари — в этой. Вот так можно легко узнать возраст Андрея из прошлого раздела:
print(data["age"])
В консоли получим:
42
Также в файле есть так называемые вложенные структуры. Это данные детей Андрея. Их тоже довольно просто получить:
print(data["children"]["son"]["name"])
Получим:
Victor
Или для дочери:
print(data["children"]["daughter"]["age"])
Результат:
12
С помощью цикла for можно получать элементы списка. У нас как раз в качестве значения ключа hobbies содержится список из трех элементов. Вот так их можно все обойти:
for hobby in data['hobbies']: print(hobby)
В консоли будет выведено:
programming reading drawing
Добавление, изменение и удаление данных
Итак, у нас есть файл data.json и мы уже знаем, как получать из него некоторые данные. Теперь давайте попробуем эти данные изменить, а может даже вообще удалить. Для начала добавим данные о работе Андрея:
data["work"] = "Big Company"
Чтобы проверить, что данные добавлены, делаем так:
print(data)
В консоли получим такой вывод:
{'firstName': 'Andrey', 'lastName': 'Ivanov', 'hobbies': ['programming', 'reading', 'drawing'], 'age': 42, 'children': {'son': {'name': 'Victor', 'age': 7}, 'daughter': {'name': 'Olga', 'age': 12}}, 'work': 'Big Company'}
Как видим, данные о работе успешно добавились. Теперь изменим возраст Андрея:
data["age"] = 43
Получим:
{'firstName': 'Andrey', 'lastName': 'Ivanov', 'hobbies': ['programming', 'reading', 'drawing'], 'age': 43, 'children': {'son': {'name': 'Victor', 'age': 7}, 'daughter': {'name': 'Olga', 'age': 12}}, 'work': 'Big Company'}
То есть, если мы присваиваем значение к несуществующим данным, то они добавляются, а если мы обращаемся к уже имеющимся данным и пытаемся их изменить, то они меняют свое значение. Для удаления данных применяется оператор del. Удалим, например, фамилию Андрея:
del data["lastName"]
Вывод в консоли будет следующий:
{'firstName': 'Andrey', 'hobbies': ['programming', 'reading', 'drawing'], 'age': 43, 'children': {'son': {'name': 'Victor', 'age': 7}, 'daughter': {'name': 'Olga', 'age': 12}}, 'work': 'Big Company'}
Но не стоит забывать, что мы изменили данные только в переменной data. Сам файл data.json остался без изменений. Чтобы записать данные в файл нужно применить ту же функцию open, но уже с параметром w (для записи) и вызвать еще функцию dump и ей на вход передать в качестве аргументов файл и данные:
with open('data.json', 'w') as file:
json.dump(data, file, indent=4)
Параметр indent задает количество пробелов для отступов на каждом уровне вложенности в файле JSON. Это делает его более читабельным. Без этого параметра все данные будут записаны в одну строку. В результате получим файл с таким содержимым:
{
"firstName": "Andrey",
"hobbies": [
"programming",
"reading",
"drawing"
],
"age": 43,
"children": {
"son": {
"name": "Victor",
"age": 7
},
"daughter": {
"name": "Olga",
"age": 12
}
},
"work": "Big Company"
}
Валидация JSON
Часто бывает полезно проверить JSON на соответствие некоторому шаблону. Узнать, все ли обязательные данные на месте и правильно заполнены. Для этого применяются JSON-схемы. Они описывают требуемую структуру и типы данных, которые должны быть в документе. Вот пример такой схемы:
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "number"},
"isWorker": {"type": "boolean"}
},
"required": ["name", "age"]
}
Мы видим, что документ в формате JSON может состоять из ключей name, age и isWorker, причем строго прописаны типы этих ключей. Так, для name принимается только строковый тип (string), а для age числовой (number). Ключ isWorker может принимать только логические значения (boolean). Также в required определены обязательные данные, которые должны входить в состав документа. В нашем случае это name и age. Для использования таких схем существует библиотека jsonschema. Импортируем ее:
from jsonschema import validate, ValidationError
Сразу после импорта прописываем нашу схему и сразу после нее приводим то, что будем проверять на валидность. Для примера возьмем вот такую простенькую нотацию:
data = {
"name": "Alex",
"age": 45,
"isWorker": True
}
Для проверки используется вот такая конструкция с обработкой исключений:
try:
validate(instance=data, schema=schema)
print("Соответствует схеме!")
except ValidationError as e:
print("Не соответствует схеме:", e.message)
Понятно, что в случае с приведенной выше нотацией проверка должна пройти успешно, так как все обязательные данные на месте и все значения указаны верно. В консоли получим:
Соответствует схеме!
А теперь давайте удалим данные с указанием возраста:
data = {
"name": "Alex",
"isWorker": True
}
Получим следующий результат:
Не соответствует схеме: 'age' is a required property
Или же оставим age, но значение возраста укажем в строковом виде:
data = {
"name": "Alex",
"age": "45",
"isWorker": True
}
Результат:
Не соответствует схеме: '45' is not of type 'number'
Получение данных с сервера
Многие сайты хранят свои данные в формате JSON. Например, есть вот такая страничка, где хранятся курсы валют и их довольно легко оттуда получить. Как это сделать? Для этого можно использовать библиотеку requests. Она поможет нам создать соответствующий запрос и получить данные. Подробнее о ней можно прочитать в этой нашей публикации. Импортируем библиотеку:
import requests
А теперь создаем GET-запрос по адресу страницы:
data = requests.get('https://www.cbr-xml-daily.ru/daily_json.js').json()
И далее выводим полученные данные в консоль. Для доллара США это будет выглядеть так:
print(data['Valute']['USD'])
Результат в консоли будет примерно такой:
{'ID': 'R01235', 'NumCode': '840', 'CharCode': 'USD', 'Nominal': 1, 'Name': 'Доллар США', 'Value': 81.3475, 'Previous': 81.3582}
Чтобы получить информацию по курсу евро, делаем так:
print(data['Valute']['EUR'])
Результат:
{'ID': 'R01239', 'NumCode': '978', 'CharCode': 'EUR', 'Nominal': 1, 'Name': 'Евро', 'Value': 94.6656, 'Previous': 94.3845}
Парсинг больших JSON-файлов
На практике, довольно часто, приходится иметь дело с очень большими по объему файлами в формате JSON. Если ресурсы ограничены, то обрабатывать такие файлы лучше всего поэтапно. Для этого имеется библиотека ijson. Она предназначена для итеративного парсинга и позволяет вести обработку файлов с большими объемами данных, не загружая их полностью в память, что как раз полезно в случае с ограниченными ресурсами. Для примера возьмем тот же наш файл data.json и пропарсим данные children:
import ijson
with open('data.json', 'r') as file:
parser = ijson.items(file, 'children')
for children in parser:
print(children)
После импорта нужного модуля, открываем файл для чтения при помощи уже знакомой нам функции open. Далее посредством функции items из импортированного модуля ijson создаем переменную с именем parser. На вход функции items подаем файл и то место в файле, которое нужно пропарсить. В результате в переменной parser окажутся необходимые нам данные. А дальше циклом for пробегаемся по всем этим данным и выводим их в консоль. В консоли получим следующее:
{'son': {'name': 'Victor', 'age': 7}, 'daughter': {'name': 'Olga', 'age': 12}}
Данные получены.
Превращение XML в JSON
Кроме JSON имеются и другие форматы для хранения и передачи данных. Одним из них является XML. Он более громоздкий и в отличие от JSON его не так удобно читать. Вот пример простого XML:
<book>
<genre>Фантастический роман</genre>
<title>Красная планета</title>
<author>Роберт Хайнлайн</author>
</book>
В языке Python есть достаточно несложный способ превращения XML в JSON. Воспользуемся библиотекой xmltodict. Она превращает XML в словарь, который потом легко преобразовать в JSON. Для начала импортируем нужные модули:
import xmltodict import json
Создадим переменную с нашим XML:
xml_data = """
<book>
<genre>Фантастический роман</genre>
<title>Красная планета</title>
<author>Роберт Хайнлайн</author>
</book>
"""
С помощью функции parse из модуля xmltodict превращаем XML в словарь:
data = xmltodict.parse(xml_data)
И теперь мы можем полученный словарь легко превратить в JSON при помощи функции dumps из модуля json:
result = json.dumps(data, indent=2, ensure_ascii=False)
Обратим внимание на следующее: в одном из прошлых разделов мы использовали функцию dump для записи данных в ФАЙЛ JSON. Функция dumps преобразует данные в СТРОКУ в формате JSON. Сравним с load и loads: функция load использовалась нами для получения данных из ФАЙЛА, а при помощи loads мы получали данные из СТРОКИ. Окончание «s» у названных функций как раз указывает на то, что они работают со строками. Выводим результат в консоль:
print(result)
В нашем случае результат в консоли будет таким:
{
"book": {
"genre": "Фантастический роман",
"title": "Красная планета",
"author": "Роберт Хайнлайн"
}
}
Задание для самостоятельного выполнения
Ранее было показано, как получить с сервера данные о курсах валют. Получите такие данные для юаня. Далее получите текущее значение курса и предыдущее его значение.
Так как юань обозначается аббревиатурой CNY, то код будет таким:
import requests
data = requests.get('https://www.cbr-xml-daily.ru/daily_json.js').json()
print(data['Valute']['CNY'])
Вывод:
{'ID': 'R01375', 'NumCode': '156', 'CharCode': 'CNY', 'Nominal': 1, 'Name': 'Юань', 'Value': 11.0648, 'Previous': 11.3079}
Значение текущего курса содержится в ключе Value и чтобы его получить надо сделать так:
print(data['Valute']['CNY']['Value'])
Результат:
11.0648
А предыдущее значение курса находится в ключе Previous. Меняем Value на Previous:
print(data['Valute']['CNY']['Previous'])
Получим:
11.3079
