В основе работы Ajv лежит преобразование JSON Schema в высокопроизводительные JavaScript-функции. Вместо интерпретации схемы во время каждой проверки данных библиотека один раз компилирует её в код, который затем выполняется напрямую. Такой подход и называется inline-компиляцией: логика валидации «встраивается» в сгенерированную функцию без промежуточных абстракций.
Каждая схема в Ajv проходит этап компиляции, на котором JSON-структура превращается в JavaScript-код. Этот код описывает последовательность проверок значений, условий типов, ограничений и ссылок на другие схемы.
Вместо обхода дерева схемы при каждом вызове проверки формируется специализированная функция:
typeof-условиями;allOf, anyOf,
oneOf) становятся ветвлениями;$ref разрешаются в вызовы других
скомпилированных функций.Результатом является функция, эквивалентная ручной реализации валидации.
Inline-компиляция в Ajv означает, что логика схемы не остаётся в виде структуры данных, а внедряется непосредственно в тело функции.
При компиляции создаётся строка кода, которая затем превращается в функцию через механизм Jav * aScript:
new Function(...).Такой подход позволяет избежать накладных расходов на интерпретацию схемы в рантайме.
Ключевая особенность — отсутствие универсального интерпретатора. Каждая схема превращается в уникальный валидатор, оптимизированный под конкретную структуру данных.
import Ajv from "ajv";
const ajv = new Ajv();
const schema = {
type: "object",
properties: {
age: { type: "number", minimum: 18 }
},
required: ["age"]
};
const validate = ajv.compile(schema);
validate({ age: 25 }); // true
validate({ age: 15 }); // false
В момент вызова ajv.compile(schema) происходит генерация
функции, которая примерно эквивалентна:
function validate(data) {
if (typeof data !== "object" || data === null) return false;
if (!("age" in data)) return false;
if (typeof data.age !== "number") return false;
if (data.age < 18) return false;
return true;
}
Генератор кода Ajv формирует валидатор по блокам:
Каждый блок вставляется напрямую в тело функции, что и формирует inline-стиль исполнения.
После компиляции Ajv сохраняет результат, чтобы избежать повторной генерации кода для одной и той же схемы.
Механизмы кэширования включают:
$id;$ref.ajv.addSchema(schema, "userSchema");
const v1 = ajv.getSchema("userSchema");
const v2 = ajv.getSchema("userSchema");
Обе функции используют один и тот же скомпилированный валидатор.
При наличии ссылок $ref компилятор не дублирует код, а
вставляет вызовы уже сгенерированных функций.
const addressSchema = {
type: "object",
properties: {
city: { type: "string" }
}
};
const userSchema = {
type: "object",
properties: {
address: { $ref: "addressSchema" }
}
};
После компиляции addressSchema становится отдельной
функцией, а userSchema вызывает её внутри своего тела.
Это предотвращает разрастание кода и сохраняет производительность.
Ajv использует динамическую генерацию функций:
const validate = new Function("data", generatedCode);
где generatedCode — строка, содержащая весь алгоритм
проверки.
Такой подход обеспечивает:
Inline-компиляция даёт значительный прирост производительности по сравнению с интерпретацией JSON Schema.
Причины:
На практике это означает, что после первой компиляции валидация становится близкой по скорости к ручному JavaScript-коду.
Без компиляции:
С inline-компиляцией:
Таким образом, Ajv смещает нагрузку на этап инициализации.
Inline-компиляция может использоваться на этапе сборки проекта, формируя полностью автономные валидаторы.
import Ajv from "ajv";
import standaloneCode from "ajv/dist/standalone";
const ajv = new Ajv();
const schema = { type: "string" };
const validate = ajv.compile(schema);
const moduleCode = standaloneCode(ajv, validate);
Результатом становится файл, содержащий готовую функцию без зависимости от Ajv.
Такой подход используется для:
При использовании bundler’ов (Webpack, Rollup, Vite) скомпилированные схемы могут быть встроены в конечный бандл как обычные функции.
Это позволяет:
Использование динамической генерации кода накладывает ограничения:
Function constructor (CSP)
блокирует выполнение;В таких случаях требуется альтернативная стратегия или предкомпиляция.
Ajv позволяет расширять систему через пользовательские keyword’ы, которые также участвуют в inline-компиляции.
ajv.addKeyword({
keyword: "even",
type: "number",
code(cxt) {
const { data } = cxt;
cxt.fail(`(${data} % 2 !== 0)`);
}
});
Такие расширения встраиваются прямо в сгенерированную функцию, сохраняя модель inline-компиляции.
Скомпилированная функция может использоваться многократно без повторной генерации:
При работе со сложными структурами:
allOf, anyOf)
превращаются в цепочки проверок.Это приводит к формированию специализированного кода для каждой структуры данных.
Inline-компиляция включает ряд оптимизаций:
Эти оптимизации выполняются на этапе генерации кода, а не во время выполнения.