В JSON два одинаковых ключа: отклоняем неоднозначную конфигурацию
В конфигурацию вручную добавили второй timeout. JSON читается без ошибки, но приложение использует последнее значение. Проверка схемы получает уже готовый словарь и не видит, что в исходном тексте ключ встречался дважды. Проверять уникальность имён после json.loads поздно: первое значение уже заменено.
Стандартный декодер Python допускает повторяющиеся имена и по умолчанию оставляет последнее. Если для конфигурации нужна однозначность, передайте object_pairs_hook. Он получает упорядоченную последовательность пар каждого объекта до создания словаря. В этом месте можно заметить повтор и прервать чтение с понятным сообщением.
Пары до создания словаря
Ниже обработчик формирует dict, но перед присваиванием проверяет наличие имени. Он вызывается и для вложенных объектов, поэтому правило действует по всему документу. Два одинаковых имени в разных объектах не конфликтуют: каждый вызов строит собственный словарь. Это соответствует области уникальности ключей внутри отдельного JSON-объекта.
import json
def unique_object(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValueError(f"duplicate key: {key}")
result[key] = value
return result
assert json.loads('{"timeout": 3}', object_pairs_hook=unique_object) == {"timeout": 3}
try:
json.loads('{"timeout": 3, "timeout": 9}', object_pairs_hook=unique_object)
except ValueError as error:
print(error)
else:
raise AssertionError("duplicate must fail")
Синтаксис, дубликаты и схема
Пример запускается на Python 3.12 и печатает duplicate key: timeout. Попробуйте обычный json.loads того же текста: получится {'timeout': 9}. Затем проверьте вложенный объект с дубликатом и два соседних объекта с одинаковыми допустимыми именами. Так тест не превратит локальное правило в ошибочный запрет повторения слова во всём документе.
Декодирование и проверка структуры остаются двумя этапами. Уникальный timeout ещё может оказаться строкой, отрицательным числом или неизвестным полем. После чтения проверьте ожидаемые поля, типы и диапазоны. Порядок удобен такой: ограничить размер входа, разобрать документ без неоднозначных ключей, проверить схему, построить объект настроек.
Не ищите повторяющиеся ключи регулярным выражением. В JSON есть строки с экранированием, вложенность и одинаковые имена в разных областях. Готовый декодер уже разбирает синтаксис; hook позволяет изменить ровно нужное правило построения объектов. У него есть и полезная диагностическая граница: ошибка относится к исходному документу, а не к поведению сервиса после загрузки.
Если формат конфигурации существовал давно, проверьте реальные сохранённые файлы до включения строгого режима. Раньше дубликаты могли незаметно задавать последнее значение. При переходе нужно оставить только намеренное значение и показать пользователю, какой ключ исправить. В тестах сохраните именно неоднозначный исходный текст, а не Python-словарь: словарь уже не способен представить два одинаковых ключа.
Следующий шаг — перенести этот приём из отдельного файла в поддерживаемое приложение. В курсе «Python для продвинутых: ООП, типизация и тестирование» структура пакетов, объектная модель, mypy и pytest соединяются в типизированном трекере задач с хранением данных и логированием. Можно отработать связь между интерфейсом, реализацией и регрессионным тестом, а затем применить её к своему проекту.
Посмотреть программу и практику курса.
Документация: официальный источник по теме.
Другие задачи по теме: Типы, тесты и поведение программы.
Продолжить на практике
- Python для продвинутых: ООП, типизация и тестирование ↗
Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.
