Валидация данных в Ajv строится на основе JSON Schema, однако стандартного набора ключевых слов часто оказывается недостаточно для сложных прикладных задач. Расширяемость библиотеки реализована через механизм пользовательских ключевых слов (custom keywords), позволяющий внедрять собственные правила валидации, трансформации данных и генерации кода схемы.
Каждое ключевое слово в Ajv — это часть компиляционного процесса схемы. При обработке JSON Schema библиотека преобразует её в функцию валидации. Пользовательские ключевые слова подключаются на этапе компиляции и могут влиять на:
Механизм расширения работает через регистрацию ключевого слова в экземпляре Ajv:
import Ajv from "ajv";
const ajv = new Ajv();
Минимальная форма определения нового ключевого слова включает функцию валидации:
ajv.addKeyword({
keyword: "isPositive",
validate: function (schema, data) {
return typeof data === "number" && data > 0;
}
});
Использование в схеме:
const schema = {
type: "number",
isPositive: true
};
const validate = ajv.compile(schema);
validate(10); // true
validate(-5); // false
В этом примере значение schema передаётся в функцию как
schema параметр, но чаще всего оно используется как флаг
или конфигурация поведения.
Ajv поддерживает несколько моделей поведения ключевых слов, каждая из которых предназначена для разных задач.
Это наиболее распространённый тип. Они возвращают true
или false и участвуют в процессе валидации:
ajv.addKeyword({
keyword: "minWords",
type: "string",
validate: function (schema, data) {
if (typeof data !== "string") return false;
return data.trim().split(/\s+/).length >= schema;
}
});
Схема:
const schema = {
type: "string",
minWords: 3
};
Для повышения производительности Ajv позволяет генерировать JavaScript-код вместо интерпретируемых функций.
ajv.addKeyword({
keyword: "multipleOf2",
type: "number",
compile: function (schema) {
return function (data) {
return data % 2 === 0;
};
}
});
Этот подход полезен, когда требуется оптимизация большого количества проверок.
Такие ключевые слова не содержат логики, а расширяют схему через под-схемы:
ajv.addKeyword({
keyword: "evenNumber",
metaSchema: {
type: "boolean"
},
validate: function (schema, data) {
return !schema || (typeof data === "number" && data % 2 === 0);
}
});
Ajv позволяет создавать более сложные ключевые слова через контекст валидации:
ajv.addKeyword({
keyword: "greaterThanField",
type: "number",
errors: true,
compile: function (schema, parentSchema) {
return function (data, dataPath, parentData, parentDataProperty) {
const compareValue = parentData[schema];
return data > compareValue;
};
}
});
Схема:
const schema = {
type: "object",
properties: {
min: { type: "number" },
max: { type: "number", greaterThanField: "min" }
}
};
Здесь используется доступ к соседним полям объекта, что невозможно выразить стандартными средствами JSON Schema.
Для детализированных сообщений об ошибках используется
errors: true и ручное формирование массива ошибок:
ajv.addKeyword({
keyword: "positive",
type: "number",
errors: true,
validate: function (schema, data) {
const valid = typeof data === "number" && data > 0;
if (!valid) {
validate.errors = [
{
keyword: "positive",
message: "значение должно быть положительным числом",
params: { value: data }
}
];
}
return valid;
}
});
Такой подход позволяет интегрировать пользовательские ключевые слова в стандартную систему ошибок Ajv.
Ajv поддерживает асинхронную валидацию, включая
async/await:
ajv.addKeyword({
keyword: "existsInDatabase",
async: true,
type: "string",
validate: async function (schema, data) {
const result = await fakeDbLookup(data);
return result.exists;
}
});
Схема:
const schema = {
type: "string",
existsInDatabase: true
};
Асинхронные ключевые слова автоматически требуют использования асинхронной функции валидации:
const validate = ajv.compile(schema);
await validate("test-value");
Ключевые слова могут принимать сложные конфигурации через JSON-структуры:
ajv.addKeyword({
keyword: "range",
type: "number",
validate: function (schema, data) {
return data >= schema.min && data <= schema.max;
}
});
Использование:
const schema = {
type: "number",
range: { min: 10, max: 20 }
};
Такой подход позволяет создавать компактные и выразительные DSL-расширения поверх JSON Schema.
Ajv компилирует схемы в функции, поэтому пользовательские ключевые
слова должны быть совместимы с этим процессом. При использовании
compile важно учитывать:
Оптимизированный вариант:
ajv.addKeyword({
keyword: "isEven",
type: "number",
compile: function () {
return function (data) {
return (data & 1) === 0;
};
}
});
Такой код выполняется быстрее за счёт битовой операции и отсутствия лишних проверок типов внутри функции.
Ключевые слова могут не только валидировать, но и изменять данные:
ajv.addKeyword({
keyword: "trim",
type: "string",
modifying: true,
compile: function () {
return function (data, dataPath, parentData, parentDataProperty) {
if (typeof data === "string") {
parentData[parentDataProperty] = data.trim();
}
return true;
};
}
});
Схема:
const schema = {
type: "string",
trim: true
};
В этом случае входное значение модифицируется до или во время валидации.
При работе с большим количеством схожих правил удобно создавать фабрики:
function createMinLengthKeyword(name, minLength) {
return {
keyword: name,
type: "string",
validate: function (schema, data) {
return typeof data === "string" && data.length >= minLength;
}
};
}
ajv.addKeyword(createMinLengthKeyword("minLen5", 5));
ajv.addKeyword(createMinLengthKeyword("minLen10", 10));
Такой подход уменьшает дублирование и повышает консистентность логики.
Пользовательские ключевые слова полностью интегрируются в процесс компиляции JSON Schema. Они:
if,
then, else);allOf, anyOf,
oneOf.Пример:
const schema = {
type: "object",
properties: {
age: { type: "number", positive: true },
score: { type: "number", isEven: true }
},
required: ["age", "score"]
};
При проектировании расширений важно учитывать ряд ограничений:
validate вместо compile;При этом корректно спроектированные ключевые слова позволяют фактически расширить JSON Schema до доменно-специфического языка валидации данных, адаптированного под конкретную прикладную область.