


В Python json.loads превращает строку JSON в объект, а json.dumps возвращает строку из объекта. Для чтения и записи открытого файла используются json.load и json.dump. Успешный разбор подтверждает допустимый синтаксис, но не наличие нужных приложению полей.
В учебной задаче Mentoring.digital прочитаем настройки с именем проекта и числом повторных попыток. Покажем отдельно границу формата, правила приложения и сохранение результата. Это помогает понять, почему корректный JSON всё равно может оказаться непригодным.
Наш формат содержит ровно два поля: непустую строку name и целое число retries от 0 до 5 включительно. Лишние поля запрещены по условию этого примера. В другом приложении политика может быть иной, но её нужно задать явно.
import json
def parse_settings(text):
data = json.loads(text)
if not isinstance(data, dict):
raise ValueError("Expected an object")
if set(data) != {"name", "retries"}:
raise ValueError("Expected name and retries")
if not isinstance(data["name"], str) or not data["name"].strip():
raise ValueError("Name must be a non-empty string")
if type(data["retries"]) is not int or not 0 <= data["retries"] <= 5:
raise ValueError("Retries must be an integer from 0 to 5")
return data
settings = parse_settings('{"name":"Учебный проект","retries":2}')
print(settings["name"])
Сначала проверяется объект верхнего уровня. JSON может содержать массив, строку или null, поэтому обращение data["name"] сразу после разбора ненадёжно. Затем проверяются состав полей, строка и число.
Условие type(value) is int выбрано намеренно: в Python bool является подклассом int, а JSON true превращается в True. Для количества попыток логическое значение нам не подходит. Строка "2" тоже не является числом по этому контракту.
from pathlib import Path
try:
text = Path("settings.json").read_text(encoding="utf-8")
settings = parse_settings(text)
except json.JSONDecodeError as error:
print(f"Invalid JSON at line {error.lineno}, column {error.colno}")
except UnicodeDecodeError:
print("Expected UTF-8 text")
except OSError as error:
print(f"Cannot read file: {error}")
except ValueError as error:
print(f"Invalid settings: {error}")
Ошибки файловой системы, кодировки, синтаксиса и структуры данных требуют разных исправлений. JSONDecodeError перехватывается до общего ValueError, поскольку наследуется от него. Не подменяйте любую ошибку пустым словарём: так потеряется причина сбоя и приложение продолжит работу с вымышленными настройками.
Не используйте eval для JSON. Одинарные кавычки, комментарии и запятая после последнего элемента не относятся к обычному JSON. Исправлять следует источник или явно выбранный формат, а не запускать содержимое файла как Python-код.
with open("settings-checked.json", "x", encoding="utf-8") as stream:
json.dump(settings, stream, ensure_ascii=False, indent=2, allow_nan=False)
Режим x создаёт новый файл и не перезаписывает существующий. Если имя занято, выберите другое или отдельно решите вопрос перезаписи. ensure_ascii=False оставляет кириллицу читаемой, а allow_nan=False запрещает нестандартные числовые значения при сериализации. Повторный вызов dump в тот же поток без собственного формата разделения не создаёт корректный единый JSON-документ.
Этот небольшой валидатор не реализует полную JSON Schema. Он также не запрещает повторяющиеся имена ключей на этапе разбора: стандартный декодер сохраняет последнее значение. Для внешнего недоверенного потока отдельно задают ограничения размера и правила повторов, а для сложного формата выбирают подходящий валидатор.
Продолжить практику с файлами и API можно на обучении Python с личным планом. На разбор с наставником по Python полезно принести обезличенный входной JSON и точный контракт, а не только сообщение об ошибке.