JSON и Python говорят на одних формах
JSON - формат по умолчанию для API, конфигов и обмена данными в вебе. К счастью для питонистов, JSON-объект ложится прямо на Python-словарь, JSON-массив - на список, JSON-строка - на str. Это близкое совпадение - причина того, что чтение и запись JSON в Python - операция в две строки.
Модуль стандартной библиотеки json обрабатывает оба направления.
Парсинг JSON-строки
json.loads(text) - «load string» - берёт строку в формате JSON и возвращает соответствующий Python-объект:
Результат - обычный словарь. К ключам обращаешься как к любому словарю - никакой специальной обёртки JSON, никакого .parse() на результате.
Если строка невалидна, json.loads поднимает json.JSONDecodeError. Сообщение включает строку и колонку проблемы - обычно этого хватает, чтобы найти пропущенную запятую или неэкранированную кавычку.
Запись JSON в строку
json.dumps(data) - «dump string» - делает обратное: берёт Python-объект и возвращает JSON-строку:
Python-овские True, False и None автоматически превращаются в JSON-ные true, false, null. Числа и строки проходят без изменений. Списки становятся массивами, словари - объектами.
Красивое форматирование
По умолчанию json.dumps выдаёт компактный вывод - ок для транспорта, тяжело людям. Передай indent=2 ради чего-то читабельного:
Ещё пара опций dumps, которые стоит знать:
sort_keys=True- отсортировать ключи объектов по алфавиту. Удобно для детерминированного вывода (конфиги, фикстуры тестов, diff-ы).ensure_ascii=False- писать не-ASCII символы (é, ü, 中) как есть, а не через\u-эскейпы. Обычно правильный выбор для UTF-8 файлов.separators=(",", ":")- самый плотный возможный вывод. В сочетании сensure_ascii=Falseдаёт самый компактный UTF-8 JSON.
Чтение JSON из файла
json.load(file) - без s - читает прямо из файлового объекта. Типичный паттерн - вместе с with open:
import json
with open("config.json") as f:
config = json.load(f)
print(config["version"])
Не нужно сначала читать файл в строку - json.load стримит через файловый объект.
Запись JSON в файл
json.dump(data, file) - файловый напарник:
import json
data = {"created": "2026-01-01", "items": [1, 2, 3]}
with open("state.json", "w") as f:
json.dump(data, f, indent=2)
Опция indent работает и тут. Открой получившийся файл - увидишь аккуратный JSON-документ.
Чтение ответа JSON API
Большинство HTTP-библиотек отдают байты или текст; ты вызываешь json.loads, чтобы превратить их в используемые данные:
import json
import urllib.request
with urllib.request.urlopen("https://api.example.com/users/1") as response:
text = response.read().decode("utf-8")
user = json.loads(text)
print(user["name"])
Библиотека requests (о ней отдельно) пропускает шаг: у объекта ответа есть метод .json(), вызывающий json.loads за тебя.
Что JSON может и не может представить
У JSON типовая система уже, чем у Python. Маппинг в обе стороны:
| Python | JSON |
|---|---|
dict | object |
list, tuple | array |
str | string |
int, float | number |
True | true |
False | false |
None | null |
Кортежи туда-обратно превращаются в списки - кортежность теряется. Множества, пользовательские классы и объекты datetime сериализовать по умолчанию нельзя - получишь TypeError: Object of type X is not JSON serializable.
Работа с datetime и своими объектами
Два частых подхода.
Сначала преобразуй в JSON-безопасную структуру. Делаем трансформацию в своём коде и уже сериализуем обычный словарь:
Передай функцию default=. json.dumps вызывает её для каждого значения, которое не умеет сериализовать:
Когда данные читаешь обратно, ISO-строки в datetime конвертируешь сам - JSON не помнит, чем они были исходно.
Раунд-трип словаря через JSON
Быстрая проверка, что словари переживают преобразование:
Значения равны, но это разные объекты. Часто именно это и нужно: json.dumps + json.loads - дешёвый способ сделать глубокую копию любой JSON-совместимой структуры.
Реалистичный пример
Короткий скрипт, который грузит JSON-конфиг, обновляет поле и сохраняет обратно:
import json
from pathlib import Path
config_path = Path("settings.json")
# Load (with a default if the file doesn't exist).
if config_path.exists():
config = json.loads(config_path.read_text())
else:
config = {"theme": "dark", "last_opened": None}
# Update.
config["last_opened"] = "2026-01-15"
# Save, pretty-printed.
config_path.write_text(json.dumps(config, indent=2, ensure_ascii=False))
Полный цикл «прочитать, изменить, записать» для JSON в десятке строк.
Несколько привычек
- Всегда
with open(...)для файлов. JSON - это просто текст; все правила работы с файлами применяются. - Предпочитай
indent=2для файлов, которые читают люди - конфиги, фикстуры, экспорты. Пропускай для сетевого трафика, где важна компактность. - Ставь
ensure_ascii=Falseдля UTF-8 вывода, чтобы имена с акцентами или нелатинскими символами оставались читаемыми. - Валидируй через
try/except json.JSONDecodeError, когда парсишь данные, которые не сам произвёл.
Дальше
JSON обрабатывает данные ключ-значение. Следующий инструмент в той же главе - CSV, встроенная поддержка табличного формата, стоящего за большинством экспортов из таблиц, что ты встретишь.
Часто задаваемые вопросы
Как распарсить JSON в Python?
Используй json.loads(text) для JSON-строки или json.load(file) для файлового объекта. Оба возвращают Python-словарь (или список - смотря что в JSON). Пример: data = json.loads('{"name": "Rosa"}') - data['name'] даст 'Rosa'.
Как преобразовать словарь Python в JSON?
json.dumps(my_dict) возвращает JSON-строку. json.dump(my_dict, file) пишет прямо в файл. Для красивого вывода передай indent=2: json.dumps(data, indent=2).
В чём разница между json.loads и json.load?
loads (с s) принимает строку. load (без s) принимает файловый объект. Аналогично для dumps vs dump. Буква s - от string, так проще запомнить.
Как обрабатывать даты и свои объекты в JSON?
В JSON нет типа для даты, так что передавай даты строками ISO-8601 и парсь их обратно вручную. Для своих классов либо передавай функцию default= в json.dumps, возвращающую JSON-безопасное представление, либо сам конвертируй в dict до сериализации.