При изменении структуры данных в приложениях, использующих JSON-документы, ключевую роль играет контроль совместимости старых и новых форматов. Любая миграция — это переход между двумя состояниями схемы, где некорректные данные способны нарушить целостность системы, привести к частичной потере информации или ошибкам исполнения бизнес-логики.
В экосистеме JavaScript для формализованной валидации JSON-структур широко применяется библиотека Ajv (Another JSON Schema Validator). Она обеспечивает строгую проверку соответствия данных JSON Schema и позволяет встроить этап валидации непосредственно в процесс миграции.
JSON Schema выступает контрактом между версиями данных. Перед миграцией фиксируется две схемы:
Ajv компилирует эти схемы в оптимизированные функции проверки, что позволяет выполнять массовую валидацию без существенных накладных расходов.
Пример базовой схемы пользователя:
const schemaV1 = {
type: "object",
properties: {
id: { type: "string" },
name: { type: "string" }
},
required: ["id", "name"],
additionalProperties: false
};
Целевая версия схемы:
const schemaV2 = {
type: "object",
properties: {
id: { type: "string" },
fullName: { type: "string" },
createdAt: { type: "string", format: "date-time" }
},
required: ["id", "fullName"],
additionalProperties: false
};
Ajv предоставляет режимы, позволяющие повысить строгость проверки и предотвратить неявные ошибки при трансформации данных.
import Ajv from "ajv";
const ajv = new Ajv({
allErrors: true,
strict: true,
removeAdditional: false
});
Ключевые параметры:
Перед запуском трансформации выполняется первичная валидация данных по старой схеме. Это позволяет выявить уже повреждённые записи, которые не могут быть корректно перенесены.
const validateV1 = ajv.compile(schemaV1);
function validateBeforeMigration(dataset) {
const invalidRecords = [];
for (const record of dataset) {
const valid = validateV1(record);
if (!valid) {
invalidRecords.push({
record,
errors: validateV1.errors
});
}
}
return invalidRecords;
}
Такой этап формирует контрольную точку качества данных до любых преобразований.
После трансформации данных в новую модель выполняется повторная проверка уже по целевой схеме. Это обеспечивает гарантии соответствия новой структуре.
const validateV2 = ajv.compile(schemaV2);
function validateAfterMigration(transformedDataset) {
return transformedDataset.filter(record => {
return validateV2(record);
});
}
При обнаружении ошибок формируется отдельный поток обработки некорректных записей, включая логирование или откат изменений.
Миграции часто сопровождаются изменением семантики данных. Ajv позволяет выявлять следующие типы конфликтов:
Пример проблемной трансформации:
// было
{ id: "1", name: "Alex" }
// стало
{ id: "1", fullName: "Alex", createdAt: "2024-01-01T00:00:00Z" }
Если поле createdAt становится обязательным, Ajv выявит
несовместимость до завершения миграции.
Ajv поддерживает строгую проверку форматов, что особенно важно при миграциях временных данных.
const schema = {
type: "object",
properties: {
createdAt: { type: "string", format: "date-time" }
},
required: ["createdAt"]
};
Для расширенной логики применяются пользовательские форматы:
ajv.addFormat("unix-timestamp", {
type: "number",
validate: value => Number.isInteger(value) && value > 0
});
Перед валидацией часто выполняется нормализация данных, чтобы снизить количество ложных ошибок.
function normalize(record) {
return {
...record,
fullName: record.fullName || record.name,
createdAt: record.createdAt || new Date().toISOString()
};
}
Далее нормализованные данные проходят через Ajv как через финальный фильтр соответствия схемам.
В миграционных сценариях важно контролировать, что новая схема не нарушает поддержку старых данных.
const validateBackward = ajv.compile(schemaV2);
function checkBackwardCompatibility(oldDataset) {
return oldDataset.every(record => validateBackward(record));
}
Если проверка не проходит, миграция требует промежуточного слоя трансформации или версионирования API.
При работе с большими объёмами данных критично учитывать производительность. Ajv компилирует схемы в функции, что позволяет выполнять проверку с линейной сложностью.
Оптимизированный поток:
function migrate(dataset) {
const valid = [];
const invalid = [];
for (let i = 0; i < dataset.length; i++) {
const normalized = normalize(dataset[i]);
if (validateV2(normalized)) {
valid.push(normalized);
} else {
invalid.push(validateV2.errors);
}
}
return { valid, invalid };
}
Strict mode в Ajv позволяет выявлять потенциальные проблемы схем ещё до выполнения валидации.
Ключевые аспекты строгой проверки:
const ajv = new Ajv({
strict: true,
strictTypes: true,
strictTuples: true
});
В типовой архитектуре миграции Ajv используется на нескольких этапах:
Такой подход формирует защитный слой между версиями схем, минимизируя риск повреждения данных при эволюции модели хранения.