Перейти к содержанию

172 уроков, 13 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 3 из 20 Начальный 30 мин 110 XP

Типы JSON и Python: таблица соответствий

Шесть пар перевода между JSON и Python: object — dict, array — list, true — True, null — None. И два типа, которые формат не понимает вовсе.

Редакция Питоники

Первые два урока показали превращение словаря туда и обратно. Но словари в реальных данных наполнены всем сразу: строками, числами, флагами, пустыми значениями. Как каждый из этих типов переживает дорогу в JSON и назад? У формата свой небольшой словарь типов — всего шесть позиций, — и модуль json переводит между двумя языками автоматически. Задача урока: увидеть всю таблицу перевода целиком и запомнить два типа, которые из Python в JSON не проедут.

Таблица соответствий

Вот полный перевод. Слева — типы JSON, как они выглядят в тексте, в центре — что вернёт Python после json.loads, справа — пример.

Тип JSONPythonПример
object — {"a": 1}dict{'a': 1}
array — [1, 2]list[1, 2]
string — "чай"str'чай'
number — 42 или 4.5int или float42 / 4.5
true / falseTrue / False (bool)флаги и переключатели
nullNoneпустое значение

Пять пунктов из шести очевидны, два стоят проговорить. Number — один тип JSON на оба питоновских: целое число вернётся int, число с точкой — float. Boolean в JSON всегда строчные: true и false — это значения, а не строки, кавычек у них нет.

Все типы в одном dumps

Соберём товар, в котором живут почти все типы сразу, и посмотрим на его json-вид.

шесть питоновских типов — один вызов
import json

item = {
    "title": "Кружка",
    "price": 490.5,
    "count": 2,
    "in_stock": True,
    "discount": False,
    "note": None,
}

print(json.dumps(item))
Вывод
{"title": "\u041a\u0440\u0443\u0436\u043a\u0430", "price": 490.5, "count": 2, "in_stock": true, "discount": false, "note": null}

Читай вывод по колонкам: True превратился в true, False — в false, None — в null, число с точкой осталось с точкой. Кириллица снова уехала в \u-коды — с урока 1 мы знаем, что это режим перевозки по умолчанию, а не порча данных. Модуль json сделал весь перевод сам: ни одного ручного преобразования в коде.

Обратная сторона: что вернёт loads

Теперь дорога назад. Подадим loads ту же смесь типов текстом и спросим типы у каждого значения.

типы после loads
import json

text = '{"count": 2, "price": 490.5, "in_stock": true, "note": null}'
data = json.loads(text)

print(type(data["count"]))
print(type(data["price"]))
print(type(data["in_stock"]))
print(type(data["note"]))
Вывод
<class 'int'>
<class 'float'>
<class 'bool'>
<class 'NoneType'>

Четыре строки — четыре питоновских типа: int, float, bool и NoneType, то есть тип значения None. Таблица работает в обе стороны: то, что dumps записал true, loads вернул как True. Для программиста это значит, что после чтения данных никаких проверок «а что это вообще такое» не нужно — типы предсказуемы.

Типы — это кирпичики, и собираться они могут как угодно: в списке лежат словари, в словаре — списки, внутри значений — те же типы снова. Перевод работает на всю конструкцию сразу, на любую глубину, поэтому таблица одна на весь раздел. Как собирать и разбирать такие комбинации — темы урока 7, там же пригодится и полное имя каждого типа из сегодняшнего вывода.

Числа: int и float

Числа заслуживают отдельного взгляда: целые проходят без сюрпризов, а вот дробные иногда возвращаются из текста с хвостом.

целые и дробные
import json

print(json.dumps(42))
print(json.dumps(3.14))
print(json.dumps(0.1 + 0.2))
Вывод
42
3.14
0.30000000000000004

Отдельная ловушка — числа в кавычках. В JSON "42" — это не число, а строка из двух символов: после loads тип будет str, и арифметика с ним упадёт. API грешат этим для телефонов, артикулов и индексов — значений, где ведущий ноль и длина важнее величины. Так что тип значения определяют не «числовость» содержания, а кавычки в тексте: есть кавычки — жди str.

Кортеж: проедет, но изменится

А теперь типы, которых в JSON нет. Первый — кортеж. dumps не падает: кортеж похож на список, и модуль записывает его массивом. Но при чтении назад массив станет списком — и это уже другой тип.

кортеж стал списком
import json

point = (55.75, 37.61)

text = json.dumps(point)
print(text)

back = json.loads(text)
print(type(point), type(back))
Вывод
[55.75, 37.61]
<class 'tuple'> <class 'list'>

Кортеж с координатами уехал в JSON массивом — квадратные скобки в тексте. Назад вернулся список. Кортеж после полного круга превращается в список: JSON хранит структуру данных, а не питоновский тип. Про исходный тип он ничего не помнит. Для координат или пар «ключ-значение» это обычно не страшно — индексы и len работают одинаково. Но если код полагается на неизменяемость кортежа или использует его как ключ словаря, разрыв вылезет.

ошибка: кортеж в роли ключа
import json

data = {(1, 2): "точка"}
print(json.dumps(data))
Вывод
TypeError: keys must be str, int, float, bool or None, not tuple
Блок не запускается намеренно: он вызывает ошибку. Список в роли ключа упадёт с той же фразой — у JSON нет ключей-контейнеров.

Множество: не переводится вовсе

Второй «непереводимый» тип — set. У JSON нет множества: есть массив, но он хранит порядок и допускает повторы, а множество — нет. Притвориться списком модуль json отказывается: слишком молчаливая подмена — порядок элементов и дубли пропали бы без предупреждения.

ошибка: множество не сериализуется
import json

tags = {"чай", "кофе"}
print(json.dumps(tags))
Вывод
TypeError: Object of type set is not JSON serializable
Блок не запускается намеренно: он вызывает ошибку. То же сообщение получат любые объекты вне таблицы: datetime, свои классы, bytes.

Ошибка Object of type set is not JSON serializable — фирменная фраза модуля json: «тип не сериализуется». Решение простое: преврати множество в список руками — json.dumps(list(tags)) — и порядок с дублями перестанут быть проблемой, потому что ты их проконтролировал сам, например отсортировав: sorted(tags). Это же сообщение ждёт тебя, если в данных затесался datetime — о превращении таких объектов подумают уроки про API, а пока держи в голове правило: JSON понимает только шесть типов из таблицы.

null и None: пустота без кавычек

null в JSON и None в Python — значение «здесь ничего нет». Оно пишется без кавычек, и его легко отличить от строки "null", которая после loads останется обычным текстом.

null читается в None
import json

data = json.loads('null')
print(data, data is None)
Вывод
None True

Проверка is None — питоновский способ спросить про пустоту, и она сработала. В живых данных null встречается постоянно: у товара нет скидки, у пользователя не заполнен телефон. После loads такие поля станут None, и код проверяет их привычным if.

Практическое следствие: перед обращением к необязательному полю его проверяют. Схема простая: достали значение, спросили if note is None, и только потом использовали. Без проверки None выстрелит позже, в самом неожиданном месте — например, при сложении с числом. В следующем уроке появится и метод .get с умолчанием, но проверка на None работает всегда и везде.

Первая программа: круг со всеми типами

Проверим таблицу целиком: соберём словарь со всеми переводимыми типами и прогоним полный круг из урока 2. Всё, что входит в таблицу, обязано вернуться равным самому себе.

полный круг со всеми типами
import json

cart = {"count": 2, "price": 490.5, "in_stock": True, "note": None}
back = json.loads(json.dumps(cart))

print(back == cart)
print(type(back["note"]))
Вывод
True
<class 'NoneType'>

Равенство подтвердило: int, float, bool и None пережили дорогу без изменений. Этот трюк — «прогни данные через dumps-loads и сравни» — годится как быстрая проверка своих структур: если круг ломается, значит где-то затесался тип вне таблицы. Приём пригодится и при чтении файлов: json.load из следующего урока вернёт те же типы, что и loads.

Что дальше

Таблица соответствий — карта всего раздела: что бы ни пришло из API и что бы ты ни сохранял в файл, типы двигаются по этим шести рельсам. Впереди: файлы JSON — dump и load для данных, которые слишком большие, чтобы жить в строке; кириллица — параметр, который вернёт русские буквы в вывод; и красивая печать для чтения сложных структур глазами. Напоминание про то, как выглядела первая выгрузка, — в уроке 1.

Шесть типов JSON — шесть пар перевода: object — dict, array — list, string — str, number — int и float, true и false — bool, null — None. Всё остальное в формат не влезает.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

import json

print(json.dumps({"on": True, "off": False, "x": None}))
import json

t = ("a", "b")
back = json.loads(json.dumps(t))
print(type(back), len(back))
import json

data = json.loads('{"price": 9.99}')
print(type(data["price"]))
Проверь себя
0 / 5

1. Во что json.loads превратит значение null?

2. Что произойдёт с кортежем (1, 2) после json.loads(json.dumps((1, 2)))?

3. Что означает ошибка Object of type set is not JSON serializable?

4. Какие питоновские типы соответствуют одному типу number в JSON?

5. Питоновское True в json-тексте выглядит как…

Карточки терминов
Запомнено: 0 / 6
Практика

Проверь таблицу соответствий на товаре: собери словарь item с ключами title («Стол»), price (7990.5), in_stock (True) и note (None). Прогони полный круг json.loads(json.dumps(item)) и выведи, равен ли результат исходному словарю, и тип значения note.

practice.py
Вопросы и ответы по уроку

Какие типы данных есть в JSON и во что они превращаются в Python?

Шесть типов: object — в dict, array — в list, string — в str, number — в int или float, true/false — в True/False (bool), null — в None. Перевод в обе стороны делает модуль json автоматически: dumps пишет питоновские значения на языке формата, loads читает их обратно.

Можно ли сохранить кортеж в JSON?

Как значение — можно, но тип не сохранится: dumps запишет кортеж массивом, а при чтении вернётся список. У JSON нет своего кортежа. Как ключ словаря кортеж не работает вовсе: dumps упадёт с keys must be str, int, float, bool or None, not tuple. Если нужна неизменяемость — храни список и не меняй, или пересобирай данные после чтения в tuple сам.

Почему Python пишет Object of type set is not JSON serializable?

В JSON нет множества: массив хранит порядок и повторы, а set — нет, и молчаливая подмена была бы опасна. Преврати множество в список руками: json.dumps(list(tags)) или sorted(tags) для предсказуемого порядка. То же сообщение появляется для datetime и собственных классов — их сериализуют строкой или словарём заранее.

Чем null в JSON отличается от пустой строки и нуля?

null — это «значения нет вообще»: после loads это None, и проверка data["note"] is None вернёт True. Пустая строка "" — строка без символов, 0 — число, оба вполне себе значения. API используют null для незаполненных полей: у товара нет скидки, у пользователя нет телефона — это не ноль, а именно отсутствие.

Понравился урок? Сошлитесь на него

«Кортеж после полного круга превращается в список: JSON хранит структуру данных, а не питоновский тип.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.