При развитии прикладных систем структура данных неизбежно меняется: добавляются новые поля, пересматриваются типы, вводятся ограничения, устаревают старые атрибуты. В контексте JSON Schema и библиотеки Ajv это приводит к необходимости не только проверять данные, но и приводить их к актуальному формату. Трансформация в этом случае становится частью валидационного конвейера, а не отдельной процедурой.
Ajv изначально ориентирован на строгую проверку, однако предоставляет набор механизмов, позволяющих модифицировать входные данные в процессе валидации или до неё.
Одним из ключевых инструментов трансформации является приведение
типов (coercion). В Ajv оно включается опцией
coerceTypes.
При включённой коэрции строковые значения автоматически приводятся к числам, булевым значениям и другим типам, если это соответствует схеме.
const Ajv = require("ajv");
const ajv = new Ajv({ coerceTypes: true });
const schema = {
type: "object",
properties: {
age: { type: "integer" }
},
required: ["age"]
};
const validate = ajv.compile(schema);
const data = { age: "42" };
validate(data);
// data.age === 42
Важная особенность заключается в том, что трансформация происходит in-place, то есть исходный объект изменяется. Это делает поведение предсказуемым в рамках одного пайплайна, но требует аккуратности при работе с неизменяемыми структурами данных.
При переходе на новые версии схем часто появляются дополнительные поля. Ajv позволяет автоматически заполнять их значениями по умолчанию.
Опция useDefaults активирует механизм внедрения значений
из ключа default в схеме.
const ajv = new Ajv({ useDefaults: true });
const schema = {
type: "object",
properties: {
role: { type: "string", default: "user" },
active: { type: "boolean", default: true }
}
};
const validate = ajv.compile(schema);
const data = {};
validate(data);
// data = { role: "user", active: true }
Этот механизм особенно полезен при миграции данных между версиями API, где новые поля должны появляться автоматически без явной постобработки.
При обновлении схемы часто возникает обратная задача — удаление
свойств, которые больше не поддерживаются. За это отвечает опция
removeAdditional.
const ajv = new Ajv({ removeAdditional: "all" });
const schema = {
type: "object",
additionalProperties: false,
properties: {
id: { type: "number" }
}
};
const validate = ajv.compile(schema);
const data = { id: 1, legacyField: "x" };
validate(data);
// data = { id: 1 }
Режим removeAdditional: "all" обеспечивает агрессивную
очистку структуры данных, приводя её к строгому соответствию новой
схеме. Это важный механизм при переходе от гибких структур к строго
типизированным контрактам.
Сложные изменения структуры данных часто зависят от значений других
полей. В Ajv для этого используется конструкция
if / then / else.
const schema = {
type: "object",
properties: {
type: { type: "string" }
},
if: {
properties: { type: { const: "admin" } }
},
then: {
properties: {
permissions: { type: "array", default: ["all"] }
}
},
else: {
properties: {
permissions: { type: "array", default: ["read"] }
}
}
};
Такая конструкция позволяет не просто валидировать данные, но и динамически формировать их структуру в зависимости от контекста.
Базовый Ajv не содержит полноценного набора трансформаций строк или
чисел, однако расширение ajv-keywords добавляет полезные
инструменты.
Наиболее часто используемое — transform.
const Ajv = require("ajv");
const addKeywords = require("ajv-keywords");
const ajv = new Ajv({ allErrors: true });
addKeywords(ajv);
const schema = {
type: "object",
properties: {
name: {
type: "string",
transform: ["trim", "toLowerCase"]
}
}
};
const validate = ajv.compile(schema);
const data = { name: " John Doe " };
validate(data);
// data.name === "john doe"
Механизм трансформации выполняется до основной валидации, что позволяет нормализовать данные без дополнительных шагов.
При необходимости сложной логики преобразования используется регистрация пользовательских ключевых слов.
ajv.addKeyword({
keyword: "toNumber",
type: "string",
modifying: true,
compile: () => (data, dataPath, parentData, key) => {
parentData[key] = Number(parentData[key]);
return true;
}
});
Использование в схеме:
const schema = {
type: "object",
properties: {
price: { type: "string", toNumber: true }
}
};
Такой подход позволяет встроить произвольную трансформацию непосредственно в процесс валидации, объединяя преобразование и проверку в единую систему.
При работе с несколькими версиями схем часто формируется многоступенчатый процесс обработки:
coerceTypes)useDefaults)removeAdditional)transform)if/then/else)Такой порядок позволяет минимизировать расхождения между источником данных и актуальной моделью.
При изменении структуры данных часто используется стратегия промежуточного преобразования. Вместо прямой валидации старых данных новой схемой применяется цепочка схем:
Каждый шаг включает частичную трансформацию.
const validateV1 = ajv.compile(schemaV1);
const validateV2 = ajv.compile(schemaV2);
function migrate(data) {
validateV1(data);
transformV1toV2(data);
validateV2(data);
}
Ajv в этом случае выполняет роль не только валидатора, но и механизма контроля корректности каждого этапа преобразования.
Особенностью трансформаций в Ajv является детерминированное изменение объекта. Любая модификация происходит синхронно в момент валидации, без скрытых побочных эффектов вне текущего вызова.
Это создаёт предсказуемую модель:
Ajv поддерживает ссылку на значения внутри данных через
$data, что позволяет строить зависимые преобразования.
const schema = {
type: "object",
properties: {
min: { type: "number" },
max: { type: "number" },
value: {
type: "number",
minimum: { "$data": "1/min" },
maximum: { "$data": "1/max" }
}
}
};
Хотя $data чаще используется для валидации, он также
косвенно влияет на структуру трансформации, задавая динамические
ограничения, которые определяют итоговое состояние данных.
Функция compile в Ajv создаёт оптимизированную функцию
валидации, внутри которой уже зафиксированы все трансформации.
const validate = ajv.compile(schema);
На этом этапе схема превращается в исполняемый код, где:
Это делает трансформации неотделимой частью выполнения, а не внешней логикой.
В Ajv трансформация не существует как отдельная абстракция. Она распределена между несколькими уровнями:
coerceTypes, useDefaults,
removeAdditional)default,
if/then/else)transform)$data)Совокупность этих механизмов формирует систему, в которой данные постепенно приводятся к актуальной структуре прямо в процессе валидации, без необходимости отдельного слоя преобразования.