Трансформация под новые схемы

При развитии прикладных систем структура данных неизбежно меняется: добавляются новые поля, пересматриваются типы, вводятся ограничения, устаревают старые атрибуты. В контексте 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, то есть исходный объект изменяется. Это делает поведение предсказуемым в рамках одного пайплайна, но требует аккуратности при работе с неизменяемыми структурами данных.


Автозаполнение значений через useDefaults

При переходе на новые версии схем часто появляются дополнительные поля. 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" обеспечивает агрессивную очистку структуры данных, приводя её к строгому соответствию новой схеме. Это важный механизм при переходе от гибких структур к строго типизированным контрактам.


Поведенческая трансформация через if/then/else

Сложные изменения структуры данных часто зависят от значений других полей. В 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"

Механизм трансформации выполняется до основной валидации, что позволяет нормализовать данные без дополнительных шагов.


Пользовательские трансформации через кастомные keywords

При необходимости сложной логики преобразования используется регистрация пользовательских ключевых слов.

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 }
  }
};

Такой подход позволяет встроить произвольную трансформацию непосредственно в процесс валидации, объединяя преобразование и проверку в единую систему.


Пайплайн трансформации при эволюции схем

При работе с несколькими версиями схем часто формируется многоступенчатый процесс обработки:

  1. Приведение типов (coerceTypes)
  2. Заполнение значений (useDefaults)
  3. Удаление лишних полей (removeAdditional)
  4. Нормализация строк (transform)
  5. Условная трансформация (if/then/else)
  6. Финальная валидация

Такой порядок позволяет минимизировать расхождения между источником данных и актуальной моделью.


Миграция между версиями схем

При изменении структуры данных часто используется стратегия промежуточного преобразования. Вместо прямой валидации старых данных новой схемой применяется цепочка схем:

  • схема v1 → схема v2 → схема v3

Каждый шаг включает частичную трансформацию.

const validateV1 = ajv.compile(schemaV1);
const validateV2 = ajv.compile(schemaV2);

function migrate(data) {
  validateV1(data);
  transformV1toV2(data);
  validateV2(data);
}

Ajv в этом случае выполняет роль не только валидатора, но и механизма контроля корректности каждого этапа преобразования.


Детерминированность изменений и модификация данных

Особенностью трансформаций в Ajv является детерминированное изменение объекта. Любая модификация происходит синхронно в момент валидации, без скрытых побочных эффектов вне текущего вызова.

Это создаёт предсказуемую модель:

  • входные данные изменяются строго по правилам схемы
  • порядок применения правил влияет на итоговый результат
  • повторная валидация может изменить объект ещё раз при другой конфигурации

Использование $data для динамической трансформации

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)
  • пользовательские keywords
  • динамические ссылки ($data)

Совокупность этих механизмов формирует систему, в которой данные постепенно приводятся к актуальной структуре прямо в процессе валидации, без необходимости отдельного слоя преобразования.