Чтобы записать данные в JSON-файл в Python, откройте файл на запись и передайте словарь в json.dump(): with open("user.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=4). Модуль json встроен в Python, ставить ничего не нужно. Параметр ensure_ascii=False сохраняет кириллицу буквами, а не кодами вида \u0410, а indent=4 раскладывает файл по строкам с отступами.
Ниже разберём пошаговую запись, разницу между dump() и dumps(), настройки форматирования, запись дат и своих классов, дописывание в готовый файл и ошибки, на которых чаще всего ломается код. Как устроен сам формат, мы подробно рассказали в статье что такое JSON простыми словами.
Как записать словарь в JSON-файл за четыре шага

Возьмём типичную задачу: сохранить профиль пользователя, чтобы программа прочитала его при следующем запуске.
- Подключите модуль строкой
import json. Он входит в стандартную библиотеку. - Соберите данные в словарь или список. JSON-объект в Python читается как
dict, массив какlist. - Откройте файл в режиме
"w"черезwith open(...)и сразу укажитеencoding="utf-8". Если файла нет, Python его создаст, если есть, перезапишет целиком. - Вызовите
json.dump(данные, файл)внутри блокаwith. После выхода из блока файл закроется сам.
import json
user = {
"name": "Анна",
"age": 29,
"skills": ["Python", "SQL"],
"remote": True,
"manager": None,
}
with open("user.json", "w", encoding="utf-8") as f:
json.dump(user, f, ensure_ascii=False, indent=4)
В папке со скриптом появится файл user.json:
{
"name": "Анна",
"age": 29,
"skills": [
"Python",
"SQL"
],
"remote": true,
"manager": null
}
Обратите внимание на замены: True стало true, а None превратилось в null. Так выглядит запись по стандарту JSON, и программа на любом другом языке прочитает этот файл без доработок.
Режимы открытия и пути к файлам мы разобрали отдельно в инструкции как открыть файл в Python. Прочитать сохранённый JSON обратно можно функцией json.load():
import json
with open("user.json", encoding="utf-8") as f:
user = json.load(f)
print(user["name"], user["skills"])
Вывод:
Анна ['Python', 'SQL']
Дальше с user работают как с обычным словарём. Например, ключ, которого может не быть, безопасно достают через метод get().
Чем dump отличается от dumps в модуле json
У модуля две функции записи, и их постоянно путают. Разница в одной букве s от слова string: dumps() возвращает строку, а dump() пишет сразу в файл. С чтением та же история.
| Функция | Что делает | Когда нужна |
|---|---|---|
json.dump(obj, f) |
пишет JSON в открытый файл | сохранить данные на диск |
json.dumps(obj) |
возвращает JSON строкой | отправить в API, вывести в лог, положить в базу |
json.load(f) |
читает JSON из файла | загрузить сохранённые данные |
json.loads(s) |
разбирает JSON из строки | обработать ответ сервера |
import json
data = {"city": "Казань", "rooms": 2}
text = json.dumps(data, ensure_ascii=False)
print(type(text), text)
with open("flat.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False)
Вывод:
<class 'str'> {"city": "Казань", "rooms": 2}
Через dumps() тоже можно записать файл: получить строку и передать её в f.write(). Так делают, когда строку перед записью нужно проверить или изменить. В остальных случаях dump() короче.
Как записать JSON с отступами, кириллицей и сортировкой
По умолчанию модуль пишет JSON в одну строку и экранирует все символы вне ASCII. Машине так нормально, а человеку такой файл читать тяжело.
Поведение меняют четыре параметра. Они одинаково работают в dump() и dumps(), полный список есть в документации модуля json.
| Параметр | По умолчанию | Что меняет |
|---|---|---|
indent |
None, всё в одну строку |
число 2 или 4 даёт отступы и перенос строк |
ensure_ascii |
True, кириллица кодами \uXXXX |
False оставляет буквы как есть |
sort_keys |
False |
True сортирует ключи по алфавиту |
separators |
(", ", ": "), а с indent (",", ": ") |
(",", ":") убирает все пробелы |
Кириллица без кодов \u
Вот что делает ensure_ascii с городом «Казань»:
import json
data = {"city": "Казань", "rooms": 2}
print(json.dumps(data))
print(json.dumps(data, ensure_ascii=False))
Вывод:
{"city": "\u041a\u0430\u0437\u0430\u043d\u044c", "rooms": 2}
{"city": "Казань", "rooms": 2}
Оба варианта корректны, при чтении Python вернёт то же слово. Но в первом случае найти нужную запись глазами в редакторе уже не выйдет. Откуда берутся такие коды и чем Юникод отличается от UTF-8, мы объясняли в статье про кодировки и кракозябры.
Отступы, сортировка и JSON в одну строку
import json
order = {"id": 512, "items": ["чай", "сахар"], "paid": True}
print(json.dumps(order, ensure_ascii=False, indent=2, sort_keys=True))
print(json.dumps(order, ensure_ascii=False, separators=(",", ":")))
Вывод:
{
"id": 512,
"items": [
"чай",
"сахар"
],
"paid": true
}
{"id":512,"items":["чай","сахар"],"paid":true}
Компактный вариант с
separators=(",", ":")экономит место, когда JSON уходит по сети или копится в логах. Для конфигов и файлов, которые будут открывать люди, удобнееindent=2илиindent=4.
Какие типы Python можно сохранить в JSON
В JSON есть только объекты, массивы, строки, числа, true, false и null. Модуль переводит типы Python в них по таблице:
| Python | JSON |
|---|---|
dict |
объект {} |
list, tuple |
массив [] |
str |
строка |
int, float |
число |
True, False |
true, false |
None |
null |
Две особенности этой таблицы ломают код уже после чтения файла:
- Кортеж вернётся списком. Записали
(3, 4), прочитали[3, 4]. Если дальше код проверяет тип, он сработает иначе. - Числовые ключи станут строками. В JSON ключ объекта всегда строка, поэтому
{1: "один"}после записи и чтения превращается в{"1": "один"}, а обращениеdata[1]падает сKeyError.
import json
data = {1: "один", "point": (3, 4), "ok": False, "empty": None}
text = json.dumps(data, ensure_ascii=False)
print(text)
print(json.loads(text))
Вывод:
{"1": "один", "point": [3, 4], "ok": false, "empty": null}
{'1': 'один', 'point': [3, 4], 'ok': False, 'empty': None}
Документация модуля предупреждает об этом прямо: если в словаре были ключи не строкового типа, после loads(dumps(x)) он может не совпасть с исходным. Ключи из файла переводите обратно в числа сами: {int(k): v for k, v in data.items()}.
Как записать в JSON даты, множества и свои классы

Всё, чего нет в таблице выше, модуль записывать отказывается. Дата, множество, Decimal или объект своего класса дают одну и ту же ошибку:
TypeError: Object of type date is not JSON serializable
Функция в параметре default
Решение: передать в параметр default функцию, которая переведёт непонятный объект в понятный. Модуль вызывает её для каждого объекта, который не смог записать сам.
import json
from datetime import date, datetime
from decimal import Decimal
def to_json(obj):
if isinstance(obj, (date, datetime)):
return obj.isoformat()
if isinstance(obj, set):
return sorted(obj)
if isinstance(obj, Decimal):
return str(obj)
raise TypeError(f"не знаю, как записать {type(obj).__name__}")
report = {
"day": date(2026, 11, 2),
"tags": {"python", "json"},
"price": Decimal("1499.90"),
}
print(json.dumps(report, default=to_json, ensure_ascii=False))
Вывод:
{"day": "2026-11-02", "tags": ["json", "python"], "price": "1499.90"}
Последняя строка функции обязательна. Если тип не обработан и функция ничего не вернула, Python получит None и запишет в файл null вместо данных, без единой ошибки.
Множество мы записали через sorted(). У set нет порядка элементов, и без сортировки содержимое файла могло бы меняться от запуска к запуску. Цену из Decimal сохранили строкой, чтобы не потерять копейки на переводе во float.
Свои классы и dataclass
Объект своего класса проще всего сначала превратить в словарь. Для dataclass это делает функция asdict() из модуля dataclasses, а для обычного класса подойдёт свой метод, который собирает словарь из нужных атрибутов. Как устроены классы, объекты и атрибуты, мы разбирали в статье ООП простыми словами.
import json
from dataclasses import dataclass, asdict
@dataclass
class Student:
name: str
score: int
students = [Student("Анна", 92), Student("Борис", 78)]
with open("students.json", "w", encoding="utf-8") as f:
json.dump([asdict(s) for s in students], f, ensure_ascii=False, indent=2)
Содержимое students.json:
[
{
"name": "Анна",
"score": 92
},
{
"name": "Борис",
"score": 78
}
]
Как дописать данные в существующий JSON-файл
Первое, что приходит в голову: открыть файл в режиме "a" и вызвать json.dump() ещё раз. Так не получится. Два объекта склеятся подряд, и файл перестанет быть валидным JSON.
import json
with open("log.json", "a", encoding="utf-8") as f:
json.dump({"step": 1}, f)
with open("log.json", "a", encoding="utf-8") as f:
json.dump({"step": 2}, f)
with open("log.json", encoding="utf-8") as f:
json.load(f)
В файле окажется {"step": 1}{"step": 2}, а чтение упадёт:
json.decoder.JSONDecodeError: Extra data: line 1 column 12 (char 11)
В документации json.dump() об этом есть отдельное предупреждение: повторные вызовы dump() с одним и тем же файлом дают невалидный JSON-файл.
Прочитать, изменить, записать заново
Рабочий способ для небольших файлов: прочитать всё содержимое, добавить запись в Python и перезаписать файл целиком.
import json
from pathlib import Path
path = Path("tasks.json")
tasks = json.loads(path.read_text(encoding="utf-8")) if path.exists() else []
tasks.append({"title": "Сдать отчёт", "done": False})
path.write_text(json.dumps(tasks, ensure_ascii=False, indent=2), encoding="utf-8")
print(len(tasks))
Первый запуск создаст файл и напечатает 1, второй допишет ещё одну задачу и напечатает 2. Так хранят настройки и списки дел почти все небольшие программы, включая телеграм-ботов на Python.
Очистить JSON-файл можно тем же способом: записать в него пустой список или словарь, например json.dump([], f). Совсем пустой файл модуль потом не прочитает и выдаст JSONDecodeError: Expecting value.
JSON Lines для логов и больших объёмов
Если записи только добавляются, как в журнале событий, перечитывать весь файл ради одной строки накладно. Для такого случая есть формат JSON Lines: в каждой строке файла лежит отдельное JSON-значение, а строки разделены символом \n.
import json
events = [{"user": 1, "action": "login"}, {"user": 2, "action": "buy"}]
with open("events.jsonl", "a", encoding="utf-8") as f:
for event in events:
f.write(json.dumps(event, ensure_ascii=False) + "\n")
with open("events.jsonl", encoding="utf-8") as f:
rows = [json.loads(line) for line in f]
print(rows)
Вывод:
[{'user': 1, 'action': 'login'}, {'user': 2, 'action': 'buy'}]
Режим "a" здесь безопасен: каждая строка читается отдельно. Если данные плоские, без вложенных списков и словарей, бывает удобнее формат CSV, его открывает и Excel.
Частые ошибки при записи JSON и как их исправить

- Вместо букв в файле коды
\u041a\u0430. Причина: по умолчаниюensure_ascii=True. Решение: передайтеensure_ascii=Falseи откройте файл сencoding="utf-8". UnicodeEncodeError: 'charmap' codec can't encode character. Файл открыт безencoding, и Python взял кодировку системы. В Python 3.14 и раньше она зависит от платформы, на русской Windows это cp1251, где нет эмодзи и многих символов. С версии 3.15 по умолчанию работает UTF-8, так решили в PEP 686. Решение: пишитеencoding="utf-8"явно, тогда код ведёт себя одинаково в любой версии.TypeError: Object of type ... is not JSON serializable. В данных есть тип, которого нет в таблице выше. Решение: функция вdefaultили перевод объекта в словарь до записи.JSONDecodeError: Extra dataпри чтении. В файл несколько раз писали в режиме"a". Решение: перезаписывать файл целиком или перейти на JSON Lines.- В файле
NaN, и другая программа его не читает. Python по умолчанию записываетNaNиInfinity, хотя стандарт JSON RFC 8259 такие значения не допускает. Решение: заменить их наNoneдо записи или передатьallow_nan=False, тогда модуль сразу подниметValueError. - Файл оказался пустым или обрезанным. Режим
"w"стирает старое содержимое ещё при открытии. Если программа упадёт посредиdump(), в файле останется обрывок вроде{"a": 1, "day":. Решение: писать во временный файл и заменять им оригинал, пример ниже.
Защищённая запись умещается в короткую функцию. Временный файл создаётся в той же папке, а потом os.replace() подменяет им оригинал. Согласно документации os.replace(), успешное переименование атомарно, этого требует стандарт POSIX.
import json
import os
import tempfile
def save_json(data, path):
folder = os.path.dirname(os.path.abspath(path))
with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=folder,
suffix=".tmp", delete=False) as tmp:
json.dump(data, tmp, ensure_ascii=False, indent=2)
os.replace(tmp.name, path)
save_json({"version": 2, "status": "готово"}, "settings.json")
print(open("settings.json", encoding="utf-8").read())
Вывод:
{
"version": 2,
"status": "готово"
}
Если запись сорвётся, старый settings.json останется целым. Такую функцию кладут в общий модуль проекта и вызывают вместо голого dump(), как любую свою функцию.
Где освоить Python системно
Запись JSON нужна почти в каждой рабочей задаче: настройки программ, ответы API, выгрузки для аналитики. Следом в программах обучения обычно идут запросы к API, базы данных и обработка исключений.
Если хочется пройти этот путь по программе с проверкой заданий, ниже курсы из нашего каталога. Сравнить их по цене, длительности и формату можно на странице курсов по Python, а понять, куда такие навыки ведут дальше, поможет обзор профессии Python-разработчика.
| Курс | Школа | Стоимость со скидкой | В рассрочку | Длительность | Обзор курса от Checkroi |
|---|---|---|---|---|---|
| Профессия «Python-разработчик» Перейти на сайт курса | 157 335 ₽ | 5987 ₽/мес. | 10 месяцев | Обзор курса | |
| Fullstack-разработчик на Python Перейти на сайт курса | 137 000 ₽ | 7125 ₽/мес. | 21 месяц | Обзор курса | |
| ДО Профессия Python-разработчик Перейти на сайт курса | 149 001 ₽ | 3167 ₽/мес. | 10 месяцев | Обзор курса | |
| Автоматизированное тестирование на Python Перейти на сайт курса | 122 226 ₽ | 4108 ₽/мес. | 9 месяцев | Обзор курса | |
| Профессия Python-разработчик Перейти на сайт курса | 140 040 ₽ | 3890 ₽/мес. | 10 месяцев | Обзор курса |
Больше программ — в полном каталоге курсов по Python




