Валидация через Ajv опирается на механизм ссылок $ref,
позволяющий выносить части JSON Schema в отдельные документы. Эти
документы могут находиться как в памяти приложения, так и быть доступны
по HTTP(S)-адресам. Поддержка удалённых схем делает возможной модульную
архитектуру описаний данных, где каждая схема переиспользуется и
хранится независимо.
$refПри встрече конструкции:
{
"$ref": "https://example.com/schemas/user.schema.json"
}
Ajv инициирует процесс резолвинга. Поведение зависит от конфигурации экземпляра валидатора:
Система разрешения строится вокруг идентификатора схемы
$id. Он используется как ключ в реестре:
{
"$id": "https://example.com/schemas/user.schema.json",
"type": "object",
"properties": {
"id": { "type": "string" }
}
}
После регистрации такой схемы все $ref на этот
$id резолвятся локально без повторных сетевых запросов.
Базовый способ работы с внешними схемами — явная регистрация:
import Ajv from "ajv";
const ajv = new Ajv();
ajv.addSchema({
$id: "https://example.com/schemas/user.schema.json",
type: "object",
properties: {
id: { type: "string" }
}
});
После добавления схема становится доступной для всех зависимых определений.
Если $ref указывает на ещё не загруженный ресурс,
поведение зависит от режима компиляции.
Удалённые схемы требуют асинхронной загрузки. Для этого используется
compileAsync и функция loadSchema.
const validate = await ajv.compileAsync(schema);
В этом режиме Ajv:
$ref;loadSchema(uri);Функция загрузки не встроена в ядро и должна быть предоставлена:
const loadSchema = async (uri) => {
const response = await fetch(uri);
if (!response.ok) {
throw new Error(`Cannot load schema: ${uri}`);
}
return await response.json();
};
Передача загрузчика:
const ajv = new Ajv({
loadSchema
});
В Node.js аналогично используется http,
https или fs в зависимости от источника.
Ajv поддерживает внутренний реестр загруженных схем:
$id или URI;Повторная загрузка одного и того же URI не выполняется. Это критично
при глубокой вложенности $ref, где граф зависимостей может
быть значительным.
Кеширование снижает:
Удалённые схемы часто образуют циклы:
// A.schema.json
{ "$ref": "B.schema.json" }
// B.schema.json
{ "$ref": "A.schema.json" }
Ajv обрабатывает такие структуры через отложенную компиляцию. При
использовании compileAsync:
Без асинхронного режима циклы приводят к ошибке разрешения.
При работе с относительными ссылками учитывается базовый URI:
{
"$id": "https://example.com/schemas/",
"$ref": "./user.schema.json"
}
Резолвинг выполняется относительно $id, что формирует
итоговый URI:
https://example.com/schemas/user.schema.json
Ошибки в $id приводят к некорректной маршрутизации
загрузки и невозможности разрешения зависимостей.
Для уменьшения количества асинхронных операций схемы могут быть загружены заранее:
await ajv.addSchema(userSchema);
await ajv.addSchema(addressSchema);
или пакетно:
ajv.addSchema([userSchema, addressSchema]);
Такой подход устраняет необходимость обращения к
loadSchema во время компиляции.
Основные факторы, влияющие на производительность:
$ref;Оптимизация достигается через:
Удалённые схемы создают риск неконтролируемого доступа к ресурсам. Основные угрозы:
$ref;Ограничение источников загрузки реализуется через фильтрацию:
const loadSchema = async (uri) => {
if (!uri.startsWith("https://trusted-domain.com/")) {
throw new Error("Blocked schema source");
}
const res = await fetch(uri);
return res.json();
};
schemaCacheAjv использует внутренний механизм хранения скомпилированных функций. При повторном использовании схемы происходит:
$ref.Это особенно важно при больших графах зависимостей, где число схем может измеряться десятками или сотнями.
Удалённые схемы корректно работают в разных версиях спецификации:
Однако поведение $id и $ref может
отличаться:
$id над именем файла;Типовые проблемы:
loadSchema;$id;Пример ошибки:
can't resolve reference https://example.com/schema.json from id #
Причина обычно связана с отсутствием регистрации или загрузки схемы в момент компиляции.
Практика проектирования схем часто комбинирует оба подхода:
Такое разделение позволяет: