В контексте Ajv макросы представляют собой способ абстрагирования и переиспользования логики JSON Schema за счёт генерации новых схем на этапе компиляции. В отличие от обычной валидации, где схема фиксирована, макросы позволяют описывать шаблон поведения, который преобразуется в стандартную JSON Schema перед выполнением проверки.
Ключевая идея заключается в том, что макрос не выполняет проверку напрямую, а возвращает модифицированную схему, которую затем использует движок Ajv.
В Ajv расширение функциональности реализуется через пользовательские
ключевые слова (custom keywords). Они могут работать в трёх
режимах:
validate — выполняет ручную проверкуcompile — генерирует функцию-валидаторmacro — преобразует схему в другую схемуИменно режим macro является основой шаблонного
подхода.
Пример базовой структуры:
ajv.addKeyword({
keyword: "range",
macro: (schema) => {
const { min, max } = schema;
return {
type: "number",
minimum: min,
maximum: max
};
}
});
Здесь ключевое слово range не проверяет значение
напрямую. Оно разворачивается в стандартные ограничения
minimum и maximum.
Макрос выполняется до компиляции схемы. Это означает:
macroТаким образом достигается:
Макросы часто используются для выражения повторяющихся ограничений.
ajv.addKeyword({
keyword: "minLengthIf",
macro: (schema) => {
const { condition, minLength } = schema;
return {
type: "string",
minLength: {
if: condition,
then: minLength,
else: 0
}
};
}
});
Хотя JSON Schema напрямую не поддерживает такую конструкцию в данном виде, макрос позволяет инкапсулировать логику на уровне декларации.
Макросы фактически превращаются в параметризованные шаблоны схем.
Пример шаблона диапазона:
ajv.addKeyword({
keyword: "between",
macro: (schema) => {
return {
type: "number",
minimum: schema[0],
maximum: schema[1]
};
}
});
Использование:
{
between: [10, 20]
}
Результат трансформации:
{
type: "number",
minimum: 10,
maximum: 20
}
Такой подход позволяет формировать компактные декларации вместо громоздких JSON Schema-объектов.
Помимо макросов, важную роль в шаблонизации играет механизм
$defs в Ajv.
Он позволяет определять повторно используемые фрагменты схем:
{
$defs: {
positiveNumber: {
type: "number",
minimum: 0
}
},
type: "object",
properties: {
price: { $ref: "#/$defs/positiveNumber" }
}
}
Макросы и $defs часто используются вместе:
$defs — для статических повторяющихся блоковmacro — для динамически генерируемых схемЕщё один уровень шаблонизации достигается через
$data-ссылки.
{
type: "object",
properties: {
min: { type: "number" },
max: { type: "number" },
value: {
type: "number",
minimum: { $data: "1/min" },
maximum: { $data: "1/max" }
}
}
}
Здесь схема зависит от значений внутри объекта.
Макросы и $data решают разные задачи:
$data делает её зависимой от входных данныхВместо макросов иногда используется композиция:
allOfanyOfoneOfПример шаблонного объединения:
{
allOf: [
{ type: "string" },
{ minLength: 5 },
{ pattern: "^[a-zA-Z]+$" }
]
}
Макрос в этом случае может упростить выражение:
ajv.addKeyword({
keyword: "alphaString",
macro: (schema) => ({
type: "string",
minLength: schema.minLength || 1,
pattern: "^[a-zA-Z]+$"
})
});
Макросы позволяют строить DSL для предметной области.
Пример: email с политиками
ajv.addKeyword({
keyword: "businessEmail",
macro: (schema) => {
const domain = schema.domain;
return {
type: "string",
format: "email",
pattern: `@${domain}$`
};
}
});
Использование:
{
businessEmail: { domain: "company.com" }
}
Несмотря на гибкость, макросы имеют ограничения:
В сложных проектах макросы должны использоваться как слой синтаксического сахара, а не как основной механизм валидации.
В крупных приложениях макросы обычно выделяются в отдельный модуль:
/schemas
/keywords
range.js
between.js
businessEmail.js
/defs
primitives.js
И регистрируются централизованно:
import { addRange } from "./keywords/range";
import { addBetween } from "./keywords/between";
export function registerKeywords(ajv) {
addRange(ajv);
addBetween(ajv);
}
Такой подход позволяет:
В итоге макросы формируют дополнительный слой над Ajv, позволяя:
При этом базовая JSON Schema остаётся конечной точкой исполнения, а макросы лишь трансформируют входное описание в её стандартное представление.