Примеры идут от базовой сериализации к практическому случаю, ради которого урок объединяет две темы: как класть даты в JSON и доставать их обратно. Весь вывод в комментариях получен запуском кода.
Пример 1. dumps/loads и соответствие типов
import json
data = {
"name": "Alice",
"age": 25,
"is_student": False,
"score": None,
"tags": ("python", "beginner"),
}
text = json.dumps(data, ensure_ascii=False)
print(text)
# {"name": "Alice", "age": 25, "is_student": false, "score": null, "tags": ["python", "beginner"]}
restored = json.loads(text)
print(type(data["tags"]), type(restored["tags"]))
# <class 'tuple'> <class 'list'>
print(restored)
# {'name': 'Alice', 'age': 25, 'is_student': False, 'score': None, 'tags': ['python', 'beginner']}
Что происходит: False/None становятся false/null — так того требует формат JSON, у него нет своих True/False с большой буквы. Кортеж tags сериализуется как массив и после loads возвращается уже списком — JSON не различает кортеж и список, оба превращаются в array.
Пример 2. Форматирование: indent, ensure_ascii, sort_keys
payload = {"b": 2, "a": 1, "city": "Москва"}
print(json.dumps(payload))
# {"b": 2, "a": 1, "city": "\u041c\u043e\u0441\u043a\u0432\u0430"}
print(json.dumps(payload, sort_keys=True))
# {"a": 1, "b": 2, "city": "\u041c\u043e\u0441\u043a\u0432\u0430"}
print(json.dumps(payload, ensure_ascii=False))
# {"b": 2, "a": 1, "city": "Москва"}
print(json.dumps(payload, indent=2, sort_keys=True))
# {
# "a": 1,
# "b": 2,
# "city": "\u041c\u043e\u0441\u043a\u0432\u0430"
# }
Что происходит: по умолчанию не-ASCII символы экранируются как \uXXXX — валидный JSON, но нечитаемый человеком; ensure_ascii=False печатает кириллицу как есть. sort_keys и ensure_ascii независимы друг от друга — их можно комбинировать с indent для читаемых конфигов.
Пример 3. Запись и чтение файла
from pathlib import Path
weather = {"city": "Berlin", "temperature": 22.5}
path = Path("weather.json")
path.write_text(json.dumps(weather, ensure_ascii=False, indent=2), encoding="utf-8")
print(path.read_text(encoding="utf-8"))
# {
# "city": "Berlin",
# "temperature": 22.5
# }
with path.open(encoding="utf-8") as f:
loaded = json.load(f)
print(loaded) # {'city': 'Berlin', 'temperature': 22.5}
path.unlink()
print(path.exists()) # False
Что происходит: json.dump/json.load (без s) работают с открытым файлом напрямую, а dumps/loads — со строкой. Здесь для записи выбран write_text с явным encoding="utf-8", чтобы не зависеть от кодировки системы; тот же файл открыт для чтения через open(..., encoding="utf-8"), как того требует json.load.
Пример 4. JSONDecodeError на битом JSON
bad_variants = [
'{"name": "Alice", "age": 25,}', # лишняя запятая перед }
"{'name': 'Alice'}", # одинарные кавычки
]
for variant in bad_variants:
try:
json.loads(variant)
except json.JSONDecodeError as error:
print(f"Ошибка: {error}")
# Ошибка: Expecting property name enclosed in double quotes: line 1 column 29 (char 28)
# Ошибка: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
Что происходит: оба варианта ловит одна и та же ошибка — парсер JSON требует ключи строго в двойных кавычках и не прощает висячую запятую перед закрывающей скобкой (в отличие от Python-словарей, где висячая запятая допустима). Текст сообщения указывает позицию (line/column/char) — полезно при разборе внешнего файла.
Пример 5. Что JSON не умеет сериализовать: set
try:
json.dumps({"values": {1, 2, 3}})
except TypeError as error:
print(f"TypeError: {error}")
# TypeError: Object of type set is not JSON serializable
print(json.dumps({"values": list({1, 2, 3})})) # {"values": [1, 2, 3]}
print(json.dumps({"values": {1, 2, 3}}, default=list)) # {"values": [1, 2, 3]}
Что происходит: в JSON есть только object и array, множества там нет — json.dumps не знает, как его записать. Явное list(...) — самый понятный способ обойти это; параметр default=list делает то же автоматически для всех несериализуемых объектов, которые умеют превращаться в список (это пригодится и в примере 11 для дат).
default выходит за рамки лекции — см. json.dump(..., default=...).
Пример 6. Вложенные структуры: конфиг с массивом объектов
config = {
"app": "shop",
"servers": [
{"host": "api-1", "port": 8000},
{"host": "api-2", "port": 8001},
],
}
text = json.dumps(config, indent=2)
print(text)
# {
# "app": "shop",
# "servers": [
# {
# "host": "api-1",
# "port": 8000
# },
# {
# "host": "api-2",
# "port": 8001
# }
# ]
# }
parsed = json.loads(text)
print(parsed["servers"][1]["host"]) # api-2
Что происходит: JSON вкладывается произвольно глубоко — здесь список словарей внутри словаря, типичная форма конфигурационного файла или ответа API. После loads добраться до значения можно обычной цепочкой индексов, как и в любой вложенной структуре Python (см. урок 36).
Пример 7. Создание даты и strftime
from datetime import datetime
launch = datetime(2025, 6, 15, 9, 30)
print(launch) # 2025-06-15 09:30:00
print(launch.strftime("%d.%m.%Y %H:%M")) # 15.06.2025 09:30
print(launch.strftime("%A, %d %B %Y")) # Sunday, 15 June 2025
print(launch.weekday()) # 6 — воскресенье (0 = понедельник)
Что происходит: datetime(year, month, day, hour, minute) собирает объект даты из чисел; strftime превращает его в строку по коду формата — %A/%B дают название дня недели и месяца на английском (локаль по умолчанию). weekday() возвращает номер дня недели, где понедельник — 0, поэтому воскресенье — 6.
Пример 8. strptime: строка → дата, и что будет при несовпадении формата
deadline = datetime.strptime("01.12.2025", "%d.%m.%Y")
print(deadline) # 2025-12-01 00:00:00
try:
datetime.strptime("15-06-2025", "%d/%m/%Y")
except ValueError as error:
print(f"ValueError: {error}")
# ValueError: time data '15-06-2025' does not match format '%d/%m/%Y'
Что происходит: формат во втором аргументе должен буквально совпадать со строкой, включая разделители — в строке дефисы, в формате слэши, и strptime честно об этом сообщает вместо того, чтобы угадывать. Время не указано, поэтому в deadline оно заполняется нулями.
Пример 9. timedelta: арифметика и сравнение дат
from datetime import date, timedelta
start = datetime(2025, 6, 15)
end = start + timedelta(days=14)
print(end.strftime("%Y-%m-%d")) # 2025-06-29
difference = deadline - launch
print(difference) # 168 days, 14:30:00
print(difference.days) # 168
today = date(2025, 6, 20)
print(today > start.date()) # True
Что происходит: timedelta можно и прибавлять к дате, и получать вычитанием двух дат — разница между deadline (1 декабря, полночь) и launch (15 июня, 9:30) выходит не круглым числом дней, а «168 дней и 14:30». date (без времени) и datetime сравниваются раздельно — отсюда start.date(), чтобы сравнить today с однотипным объектом.
Пример 10. Наивные и осведомлённые о часовом поясе даты
from datetime import timezone
naive = datetime(2025, 6, 15, 12, 0)
aware = datetime(2025, 6, 15, 12, 0, tzinfo=timezone.utc)
print(naive.tzinfo) # None
print(aware.tzinfo) # UTC
try:
print(naive < aware)
except TypeError as error:
print(f"TypeError: {error}")
# TypeError: can't compare offset-naive and offset-aware datetimes
Что происходит: datetime без tzinfo называется «наивным» — Python не знает, в каком часовом поясе эти 12:00. Смешивать наивные и «осведомлённые» (aware) объекты в сравнении нельзя — это частый источник багов при работе с датами из разных источников (сервер в UTC, пользователь в своём поясе).
zoneinfo.
Пример 11. Совмещаем темы: datetime внутри JSON
Ради этого сочетания урок и объединяет json и datetime: формат JSON умеет строки, числа, булевы значения — но не объекты datetime.
event = {"title": "Deploy", "when": datetime(2025, 6, 15, 9, 30)}
try:
json.dumps(event)
except TypeError as error:
print(f"TypeError: {error}")
# TypeError: Object of type datetime is not JSON serializable
serializable = {**event, "when": event["when"].isoformat()}
text = json.dumps(serializable, ensure_ascii=False)
print(text) # {"title": "Deploy", "when": "2025-06-15T09:30:00"}
restored = json.loads(text)
restored["when"] = datetime.fromisoformat(restored["when"])
print(restored) # {'title': 'Deploy', 'when': datetime.datetime(2025, 6, 15, 9, 30)}
print(type(restored["when"])) # <class 'datetime.datetime'>
Что происходит: прямая сериализация datetime падает так же, как падала сериализация set в примере 5 — оба не входят в базовый набор типов JSON. Рабочий приём — вручную превратить дату в строку через .isoformat() перед dumps и вручную же разобрать её обратно через datetime.fromisoformat() после loads; сам JSON про даты ничего не знает и не преобразует их автоматически.
Что дальше
Практика — в заданиях, включая фильтрацию оценок по датам из JSON-файла. Если что-то не совпало с ожиданием — сверьтесь с типичными ошибками.