Без additionalProperties валидация схемы ничего не проверяет
Главное: Успешная валидация не значит чистые данные. Pydantic по умолчанию игнорирует неизвестные поля, Zod их отбрасывает, Ajv пропускает — выдуманные параметры и опечатки проходят тихо. Статья показывает три формы молчаливого принятия и даёт полные рецепты: закрывать объекты, контролировать приведение типов, подтягивать каждый уровень вложенности.

Вывод: по умолчанию валидаторы игнорируют незаявленные поля, а не отклоняют их. Без additionalProperties: false успешная валидация доказывает лишь, что заявленные поля в порядке.
Самое опасное поведение валидатора — не ошибка, а тишина. Опечатка в ключе или выдуманный моделью параметр проходит сквозь валидацию бесшумно и взрывается в бизнес-логике.
Один: валидация, которая «прошла»
{
"type": "object",
"properties": {
"order_id": { "type": "string" },
"amount": { "type": "number" }
},
"required": ["order_id", "amount"]
}
Передайте {"order_id": "A1", "amount": 50, "reasn_code": "damaged"} — третий ключ с опечаткой. С настройками Ajv по умолчанию результат — успех. Лишнее поле не отклоняется и не помечается.
В спецификации JSON Schema значение additionalProperties по умолчанию true: незаявленные поля разрешены.
Два: три варианта молчания
| Среда | Поведение по умолчанию | Последствие |
|---|---|---|
| Ajv (JS) | пропускает | грязные данные доходят до логики |
Zod z.object |
отбрасывает | поле исчезает, объект укорачивается |
| Pydantic v2 | игнорирует (extra="ignore") |
то же, без следов в логах |
Отбрасывание скрытнее пропуска. Обе библиотеки по умолчанию проваливаются одинаково — молча.
Три: вызовы инструментов LLM усугубляют проблему
Аргументы генерирует модель: она уверенно придумывает имена полей и передаёт "3" вместо числа. Платформы определяют параметры через JSON Schema, но по умолчанию не заставляют схему быть закрытой — strict-режим нужно включать явно, а грамматические ограничения покрывают только прямой вывод модели.
{
"order_id": 4411,
"refund_amount_usd": 50,
"reason_code": "damaged",
"urgent": "yes"
}
С закрытым объектом reason_code и urgent отклоняются, и ошибка указывает на путь /reason_code.
Четыре: как закрыть
Ajv: добавьте additionalProperties: false на узел объекта.
import Ajv from "ajv";
const ajv = new Ajv({ allErrors: true });
const schema = {
type: "object",
properties: { order_id: { type: "string" }, amount: { type: "number" } },
required: ["order_id", "amount"],
additionalProperties: false,
};
const validate = ajv.compile(schema);
validate({ order_id: "A1", amount: 50, reasn_code: "x" }); // false
Zod: z.strictObject или .strict(). Pydantic: ConfigDict(extra="forbid").
Общее правило: закрытие выполняется на каждом уровне. Каждый вложенный объект требует своего additionalProperties: false.
Пять: политика приведения типов
Lax-режим Pydantic превращает "5" в 5. Три политики: строгая (отклонять неверные типы), контролируемая (только явные ограниченные преобразования), свободная (лишь для внутренних данных). Для ненадёжных входов выбирайте первую или вторую и фиксируйте в тестах.
Шесть: чек-лист вложенности
| Пункт | Действие |
|---|---|
| Корневой объект | additionalProperties: false |
| Каждый вложенный объект | то же |
| Элементы массивов | закрывать объекты в items |
| Enum и диапазоны | добавить enum, minimum/maximum |
| Сгенерированные схемы | дополнять вручную |
Семь: после отклонения
allErrors: true показывает все поля сразу; verbose: true добавляет пути. Синтаксические проблемы ловит парсер до валидации схемы.
FAQ
Не слишком строго?
Зависит от контракта. Для своих входов — закрывать; для сторонних данных с расширениями — закрывать только ядро и выделить зону расширения.
Strip в Zod безопаснее?
Нет: он защищает нижние слои в ущерб вызывающему. Ошибки должны проявляться рано и явно.
Strict-режим платформы заменяет локальную валидацию?
Нет. Схема может быть открытой, а грамматика может не покрывать аргументы инструментов.
После закрытия старые данные массово падают?
Это именно то, что нужно было показать. Инвентаризируйте через allErrors, добавьте безвредные поля, исправьте вредные.
Решение в одну строку?
Нет, но минимальный шаг: проверить, что за каждым type: "object" следует additionalProperties: false.