Словарь и JSON: json.loads и json.dumps
Словарь и JSON — два облика одних данных: json.loads читает строку в dict, json.dumps собирает строку из словаря, json.dump пишет в файл, а ensure_ascii=False бережёт кириллицу.
Редакция Питоники
Словарь — структура в памяти работающей программы: пары живут, пока живёт Python. Но в файл настроек словарь не положишь и по сети не отправишь — за пределами программы остаётся только текст. Для упаковки данных в текст есть общемировой формат JSON, и у него с словарём особые отношения: снаружи — строка, внутри — те же пары, списки и числа. Модуль json входит в стандартную поставку Python и умеет ездить в обе стороны; в этом уроке — обе дороги: из строки в словарь и из словаря в строку.
json.loads: строка в словарь
import json
raw = '{"city": "Сочи", "temp": 24}'
data = json.loads(raw)
print(data)
print(data['city'])
print(type(data).__name__)
{'city': 'Сочи', 'temp': 24}
Сочи
dictСмотрите на кавычки внимательно — их здесь два уровня. Питоновская строка raw обёрнута в одинарные кавычки, а внутри неё — JSON-текст с двойными: таковы правила формата, строки в JSON всегда в двойных кавычках. Функция json.loads прочитала этот текст и вернула настоящий словарь: type честно отвечает dict, доступ по ключу работает, и дальше это обычный словарь со всем, что мы про него знаем.
json.dumps: словарь в строку
import json
book = {'title': 'Сказки', 'pages': 120}
text = json.dumps(book)
print(text)
print(type(text).__name__)
{"title": "\u0421\u043a\u0430\u0437\u043a\u0438", "pages": 120}
strОбратная дорога прошла, но взгляд цепляется за \u0421 и другие коды вместо «Сказки». Это не поломка: по умолчанию json.dumps кодирует все не-латинские буквы последовательностями \uXXXX, чтобы текст безопасно проходил через любые каналы и программы. Кракозябры — не поломка: данные не испорчены, и любой парсер развернёт коды обратно в кириллицу. Итог вызова — обычная строка: её можно записать в файл, отправить по сети, посчитать её длину через len.
ensure_ascii=False: кириллица остаётся
import json
book = {'title': 'Сказки', 'pages': 120}
print(json.dumps(book, ensure_ascii=False))
{"title": "Сказки", "pages": 120}indent: вывод для человека
import json
book = {'title': 'Сказки', 'pages': 120, 'tags': ['детям', 'классика']}
print(json.dumps(book, ensure_ascii=False, indent=2))
{
"title": "Сказки",
"pages": 120,
"tags": [
"детям",
"классика"
]
}Что происходит со списками и вложенными объектами
Многие новички думают, что JSON — это словарь и только. На самом деле у JSON два типа структур: объект (в Python — словарь) и массив (в Python — список). В ответе API они почти всегда идут вместе: сверху объект с метаданными, внутри — массив объектов. Разбирать такой ответ приходится в два шага, и словарь тут только верхний уровень.
import json
raw = '{"items": [{"id": 1}, {"id": 2}], "total": 2}'
data = json.loads(raw)
print(data['total'])
print(data['items'][0]['id'])
print(type(data).__name__)
2 1 dict
Разбор по слоям. data — обычный словарь, верхний уровень ответа. data['items'] — список из двух словарей, каждый со своим ключом id. Дальше data['items'][0] даёт первый словарь в списке, а ['id'] — значение внутри него. Заметьте: тип data — dict, и никакой особый тип JsonResponse в Python не появляется. Дальше работают все обычные приёмы — get, перебор items(), сортировка.
Данные, которые JSON не умеет, тоже встречаются. Множество (set) и кортеж в JSON не поддерживаются: кортеж превратится в список, а множество вызовет ошибку. Если в словаре лежит datetime, dumps тоже споткнётся — для дат нужен либо default=str, либо ручное приведение к строке до сериализации.
import json
try:
print(json.dumps({'tags': {'a', 'b'}}))
except TypeError as e:
print('TypeError:', e)
TypeError: Object of type set is not JSON serializable
import json
print(json.dumps({'point': (1, 2)}))
{"point": [1, 2]}Про кортеж стоит помнить: на выходе в JSON это уже список, и при обратном чтении вы получите не кортеж, а список. Данные «проехали туда-обратно» и сменили тип — типичная неожиданность, когда код ждёт кортеж, а получает список. Множество же не поедет вовсе: dumps честно откажется. Ошибка здесь лучше тихой подмены типа, но знать о ней надо заранее — особенно если в JSON кладут результат set() или zip().
round-trip: словарь проходит JSON и возвращается
Главное свойство JSON-текста: если он получен из словаря через dumps, то loads вернёт словарь, равный исходному. Это называется round-trip, и именно ради него формат и придуман: записали, передали по сети, прочитали — получили то же самое. Проверяется сравнением через ==, и словарь сравнивает пары без учёта порядка.
import json
original = {'a': 1, 'b': [1, 2], 'c': {'d': 'текст'}}
text = json.dumps(original, ensure_ascii=False)
back = json.loads(text)
print(back)
print(back == original)
{'a': 1, 'b': [1, 2], 'c': {'d': 'текст'}}
TrueРавенство True — не мелочь, а рабочая проверка: если после round-trip что-то изменилось, значит, где-то по дороге данные потеряли тип или значение. Подвох в том, что == для словарей не проверяет порядок пар, — а вот для списков проверяет, и список после JSON остаётся списком. Ещё одна тонкость: ключи в JSON всегда строки, поэтому словарь с целочисленными ключами после round-trip потеряет их тип — {1: 'a'} превратится в {'1': 'a'}, и поиск по ключу 1 перестанет работать.
Словарь в файл и обратно
У модуля json есть пара без буквы s: dump и load. dump не возвращает строку, а пишет прямо в открытый файл, load читает из файла целиком. Разница между dumps и dump — в одном символе, а в использовании разница заметная: файлу настроек не нужна промежуточная строка в памяти.
import json
import os
book = {'title': 'Сказки', 'pages': 120, 'author': 'Анна'}
with open('data.json', 'w', encoding='utf-8') as f:
json.dump(book, f, ensure_ascii=False, indent=2)
with open('data.json', encoding='utf-8') as f:
print(f.read())
os.remove('data.json')
print('файл удалён:', os.path.exists('data.json'))
{
"title": "Сказки",
"pages": 120,
"author": "Анна"
}
файл удалён: FalseРазбор по строке. Блок with open(...) сам закрывает файл — даже если внутри случится ошибка, — поэтому руками закрывать не нужно. Кодировка utf-8 обязательна: без неё русский текст на Windows запишется в системной кодировке и прочитается обратно как мусор. Параметры ensure_ascii=False и indent=2 ушли прямо в dump — работают они точно так же, как у dumps. Последняя строка убирает файл, чтобы песочница не копилась: в браузере open пишет в виртуальную файловую систему Pyodide, файлы живут до перезагрузки страницы.
import json
import os
book = {'title': 'Сказки', 'pages': 120, 'author': 'Анна'}
with open('data.json', 'w', encoding='utf-8') as f:
json.dump(book, f, ensure_ascii=False)
with open('data.json', encoding='utf-8') as f:
loaded = json.load(f)
print(loaded)
print(loaded == book)
os.remove('data.json')
{'title': 'Сказки', 'pages': 120, 'author': 'Анна'}
TrueСравнение loaded == book даёт True, и это лучший тест для конфига: если после записи и чтения словарь изменился, значит, где-то потерялись данные или типы. Отдельный json.load читает файл целиком и сразу отдаёт словарь — никакой промежуточной строки. Смысла в json.load(open(...)) без блока with немного: файл останется открытым до конца программы, а на Windows это ещё и блокировка файла.
Когда JSONDecodeError — это подсказка
Ошибка парсинга почти всегда объяснима, если знать, где искать. JSONDecodeError сообщает три вещи: чего ожидала программа, на какой строке и в какой позиции это случилось. Почти всегда виновата одна из трёх причин: одинарные кавычки вместо двойных, Python-значения True/False/None вместо true/false/null или просто пустая строка.
import json
try:
json.loads("{'name': 'Барсик'}")
except json.JSONDecodeError as e:
print('JSONDecodeError:', e)
JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
Читать это сообщение полезно с конца. line 1 column 2 (char 1) — позиция в тексте: первый символ после открывающей фигурной скобки. Expecting property name enclosed in double quotes — чего не хватило: имени ключа в двойных кавычках. То есть дело не в значении, а в кавычках. Пустая строка даёт похожий текст: Expecting value: line 1 column 1 (char 0) — ждали значение, а нашли конец текста.
| Что | В Python | В JSON | Что будет |
|---|---|---|---|
| Строка | 'текст' | "текст" | В JSON только двойные кавычки |
| Истина | True | true | Python-значения JSON не понимает |
| Ложь | False | false | То же самое |
| Ничего | None | null | В JSON именно null |
| Число | 42, 3.14 | 42, 3.14 | Формат совпадает |
| Список | [1, 2] | [1, 2] | То же самое |
| Кортеж | (1, 2) | [1, 2] | Кортеж станет списком |
| Множество | {1, 2} | — | TypeError: not JSON serializable |
| Ключ словаря | 1 (число) | "1" (строка) | Ключ всегда строка |
Что дальше
JSON разобрали — теперь словарь умеет ещё и сравниваться, копироваться и служить ключом. В уроке 19 про ==, copy и deepcopy — про то, почему два одинаковых словаря могут быть разными объектами. А если данные приходят не из файла, а из сети, и структура ответа незнакомая, — урок 17 про безопасный разбор ответа API через get.
Сначала предскажи ответ в голове — это главный навык программиста.
import json
data = json.loads('{"a": 1}')
print(data['a'])
import json
print(json.dumps({'a': 1}))
import json
print(json.dumps({'чай': 1}, ensure_ascii=False))
1. Что возвращает json.loads(строка)?
2. Что возвращает json.dumps(словарь)?
3. Почему в выводе json.dumps вместо русских букв стоят коды вида \u043c?
4. Какой параметр оставляет кириллицу как есть?
5. Парсер получил текст {'name': 'Барсик'} — с одинарными кавычками внутри. Что скажет json.loads?
6. Что произойдёт при json.dumps({'point': (1, 2)})?
7. Чем json.dump отличается от json.dumps?
8. Что будет, если словарь с целочисленными ключами пройдёт round-trip через JSON?
Дана строка order — JSON-запись заказа. Прочитай её через json.loads в словарь, выведи имя питомца по ключу 'name', затем собери из словаря JSON-строку с ensure_ascii=False и indent=2 и выведи её.
Как превратить словарь Python в JSON?
Функцией json.dumps: text = json.dumps(book, ensure_ascii=False, indent=2) вернёт JSON-строку с читаемой кириллицей и отступами. Для записи сразу в файл есть парная функция json.dump — она принимает файл вместо возврата строки.
Раздел про JSON с нуляКак прочитать JSON в словарь Python?
Функцией json.loads: data = json.loads(строка) вернёт словарь, если в тексте объект JSON. Дальше это обычный dict: доступ по ключу, get, перебор items. Для чтения из файла есть json.load — ей передают открытый файл.
Почему после json.dumps вместо русских букв стоят \u043c и похожие коды?
По умолчанию dumps кодирует все не-латинские символы последовательностями \uXXXX — так текст безопасно проходит через любые каналы. Это валидный JSON, данные целы. Параметр ensure_ascii=False оставляет кириллицу как есть.
Урок 5 раздела json: кириллицаЧем JSON отличается от словаря Python?
Словарь — живая структура в памяти, JSON — текст по строгим правилам: строки только в двойных кавычках, True и None пишутся как true и null, ключи — только строки. Python-запись {'a': 1} JSON-парсер не примет — читайте и собирайте JSON только через модуль json.
Как записать словарь в файл как JSON?
Через json.dump: with open('data.json', 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2). Кодировку utf-8 указывайте обязательно — иначе русский текст на Windows запишется в системной кодировке. Обратно файл читается через json.load(f).
Как в JSON записать русский текст, чтобы его было видно?
Параметром ensure_ascii=False: json.dumps(data, ensure_ascii=False). По умолчанию все не-латинские символы кодируются последовательностями \uXXXX — это валидный JSON, но нечитаемый. Для машинной передачи данных разницы нет, для файлов и логов — есть.
Что будет, если в словаре лежит кортеж или множество?
Кортеж молча превратится в список: json.dumps({'point': (1, 2)}) даст {"point": [1, 2]}, и после обратного чтения тип уже не восстановится. Множество вызовет TypeError: Object of type set is not JSON serializable — JSON его просто не умеет.
Как прочитать JSON из строки, полученной от API?
Функцией json.loads: data = json.loads(response.text) вернёт словарь, если в тексте объект JSON. Дальше это обычный dict: доступ по ключу, get, перебор items. Если сервис отдаёт массив, loads вернёт список словарей, и разбор пойдёт в два шага — по индексу и по ключу.
Урок 17: разбор ответа APIЧто означает ошибка Expecting property name enclosed in double quotes?
Что парсер нашёл не имя ключа в двойных кавычках. Чаще всего дело в том, что текст собран вручную с одинарными кавычками — {'name': 'Барсик'} для json.loads не JSON. Второй по частоте случай — Python-значения True, False, None вместо true, false, null.
Понравился урок? Сошлитесь на него
«Кракозябры — не поломка: данные не испорчены, и любой парсер развернёт коды обратно в кириллицу.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Словари · Урок 15
dict comprehension: словарь за строку
Dict comprehension собирает словарь за одну строку: квадраты чисел, цены со скидкой, фильтр по условию, инверсия ключей и значений — и словарь из списка через enumerate.
Словари · Урок 17
Словарь как данные: разбор ответа API
Ответ API — это вложенный словарь: достаём поля по цепочке ключей, читаем безопасно через get с умолчанием, разбираем списки записей и собираем из ответа готовый отчёт.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
json · Урок 5
Кириллица в JSON: ensure_ascii=False
Открываешь json-файл, а там \u041c\u043e\u0441\u043a\u0432\u0430: откуда берутся кракозябры, почему это не поломка и один параметр, который вернёт русские буквы.
json кириллица python ensure_asciiensure_ascii false
json · Урок 1
Что такое JSON и зачем он Python: первый json.dumps
Первое превращение словаря в json-строку одной командой: import json, json.dumps и честный взгляд на кракозябры в выводе — всё исполняется прямо на странице.
json python что эточто такое json
Словари · Урок 1
Словарь в Python: ключ и значение
Словарь — структура «ключ — значение»: достаём номер телефона по имени из телефонной книги, сравниваем dict со списком на живых данных и заглядываем под капот — как Python находит значение по ключу.
словарь pythonсловарь в питоне