Валидация JSON в Ajv основана на заранее скомпилированных схемах. Однако в реальных приложениях полный набор схем часто слишком велик для загрузки на старте. Ленивый подход заключается в том, что схема подгружается и компилируется только в момент, когда она действительно требуется для валидации конкретного объекта.
Основная идея:
$ref на внешние ресурсыAjv поддерживает этот подход через комбинацию асинхронной компиляции и механизма загрузки зависимых схем.
$ref как точка входа для ленивой загрузкиЛенивая загрузка в Ajv почти всегда начинается с использования ссылок
$ref.
{
"$id": "user.json",
"type": "object",
"properties": {
"profile": { "$ref": "profile.json" }
}
}
При встрече $ref валидатор должен:
Ключевой момент: Ajv не ограничивается локальными схемами, а может динамически подгружать внешние зависимости.
Для включения ленивого разрешения зависимостей используется опция
loadSchema.
import Ajv from "ajv";
const ajv = new Ajv({
strict: true,
loadSchema: async (uri) => {
const response = await fetch(uri);
if (!response.ok) {
throw new Error(`Не удалось загрузить схему: ${uri}`);
}
return await response.json();
}
});
loadSchemaФункция:
$refЛенивая загрузка невозможна без асинхронной компиляции.
Ajv предоставляет метод:
const validate = await ajv.compileAsync(schema);
Поведение:
$ref, они разрешаются через
loadSchemaПосле первой загрузки схема не должна компилироваться повторно.
Ajv автоматически:
$id$refДополнительно можно управлять поведением через
addSchema:
ajv.addSchema(schema, "user.json");
Это позволяет заранее прогреть кеш и уменьшить количество ленивых загрузок.
Сложные схемы часто образуют граф зависимостей:
user.json → profile.json → address.json → country.json
При ленивой загрузке Ajv выполняет:
user.json$ref → profile.jsonprofile.json$refВажно учитывать:
Без контроля одинаковые схемы могут быть запрошены несколько раз.
Типичный паттерн оптимизации:
const cache = new Map();
function loadSchema(uri) {
if (cache.has(uri)) {
return cache.get(uri);
}
const promise = fetch(uri).then(r => r.json());
cache.set(uri, promise);
return promise;
}
Такой слой часто добавляется поверх Ajv loadSchema.
В Node.js и современных сборщиках возможна ленивость на уровне модулей:
const ajv = new Ajv({
loadSchema: async (uri) => {
if (uri === "profile.json") {
const mod = await import("./schemas/profile.json", {
assert: { type: "json" }
});
return mod.default;
}
}
});
Это снижает сетевую нагрузку, если схемы поставляются вместе с приложением.
При использовании ленивой загрузки:
validate(), если есть
внешние $refТипичная ошибка:
compileAsyncПравильный подход:
const validate = await ajv.compileAsync(schema);
const valid = validate(data);
Каждый $ref может вызвать сетевой запрос, что
увеличивает время первой валидации.
Если loadSchema использует HTTP:
Глубокие цепочки $ref усложняют трассировку ошибок
схем.
В реальных проектах ленивую загрузку комбинируют с частичной предзагрузкой:
await ajv.addSchema(baseSchemas);
Через loadSchema.
Включаются в бандл приложения.
В распределённых системах схемы часто хранятся централизованно:
loadSchema становится клиентом реестра:
loadSchema: async (uri) => {
return fetch(`https://schemas.internal/${uri}`).then(r => r.json());
}
Поддержка версионирования:
user@1.2.0.jsonuser@latest.jsonЛенивая загрузка даёт выигрыш в:
Но увеличивает:
Оптимальный баланс достигается через:
$refПосле первой компиляции:
$ref заменяются на оптимизированные функцииТаким образом, ленивость влияет только на первый проход.
loadSchemaПриводит к повторным запросам одной и той же схемы.
$refПриводит к runtime-ошибкам.
A → B → C → A
Без корректного кеширования это приводит к бесконечным запросам.
$id и нормализация
URIAjv использует $id как ключ регистрации схемы.
Важно:
$idВ frontend-приложениях ленивую загрузку часто совмещают с:
import()Каждый модуль приносит свой набор JSON Schema, которые регистрируются в Ajv только при активации функциональности.