Обработка сложных структур данных

Библиотека Validator.js изначально ориентирована на проверку строковых значений, однако в реальных приложениях данные редко ограничиваются простыми строками. Чаще всего приходится работать с объектами, массивами, вложенными структурами и комбинированными формами данных, поступающими из API, форм или внешних источников.

Эффективная обработка сложных структур в таких условиях строится не на расширении самой библиотеки, а на грамотной архитектуре валидационного слоя, где Validator.js используется как базовый набор примитивных проверок.


Валидация объектов как композиция простых проверок

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

const user = {
  name: "Ivan",
  email: "ivan@mail.com",
  age: 25,
  address: {
    city: "Almaty",
    zip: "050000"
  }
};

Validator.js предоставляет набор функций для проверки отдельных значений, например:

  • isEmail
  • isLength
  • isInt
  • isEmpty

Логика валидации объекта строится вручную:

import validator from "validator";

function validateUser(user) {
  const errors = {};

  if (!validator.isLength(user.name || "", { min: 2 })) {
    errors.name = "Имя слишком короткое";
  }

  if (!validator.isEmail(user.email || "")) {
    errors.email = "Некорректный email";
  }

  if (!validator.isInt(String(user.age), { min: 0, max: 120 })) {
    errors.age = "Недопустимый возраст";
  }

  return {
    isValid: Object.keys(errors).length === 0,
    errors
  };
}

Такой подход превращает объект в набор независимых проверок, каждая из которых изолирована и предсказуема.


Обработка вложенных структур

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

function validateAddress(address) {
  const errors = {};

  if (!validator.isLength(address.city || "", { min: 2 })) {
    errors.city = "Некорректный город";
  }

  if (!validator.isPostalCode(address.zip || "", "RU")) {
    errors.zip = "Некорректный индекс";
  }

  return errors;
}

Интеграция вложенной проверки в основную структуру:

function validateUser(user) {
  const errors = {};

  const addressErrors = validateAddress(user.address || {});

  if (Object.keys(addressErrors).length > 0) {
    errors.address = addressErrors;
  }

  return {
    isValid: Object.keys(errors).length === 0,
    errors
  };
}

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


Работа с массивами данных

Массивы требуют итерационной проверки каждого элемента. Например, список email-адресов:

const data = {
  emails: ["test@mail.com", "invalid-email", "user@site.com"]
};

Валидация массива:

function validateEmails(emails) {
  const errors = [];

  emails.forEach((email, index) => {
    if (!validator.isEmail(email)) {
      errors.push({
        index,
        message: "Некорректный email"
      });
    }
  });

  return errors;
}

Интеграция в объектную структуру:

function validateData(data) {
  const errors = {};

  const emailErrors = validateEmails(data.emails || []);

  if (emailErrors.length) {
    errors.emails = emailErrors;
  }

  return {
    isValid: Object.keys(errors).length === 0,
    errors
  };
}

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


Нормализация данных перед проверкой

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

function normalizeUser(user) {
  return {
    ...user,
    name: (user.name || "").trim(),
    email: (user.email || "").toLowerCase(),
    age: String(user.age)
  };
}

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


Условная валидация полей

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

function validateUser(user) {
  const errors = {};

  if (user.type === "admin") {
    if (!validator.isLength(user.adminCode || "", { min: 5 })) {
      errors.adminCode = "Некорректный код администратора";
    }
  }

  if (user.isActive && !validator.isEmail(user.email || "")) {
    errors.email = "Email обязателен для активного пользователя";
  }

  return {
    isValid: Object.keys(errors).length === 0,
    errors
  };
}

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


Композиция валидаторов

При увеличении сложности структуры целесообразно выделять повторяющиеся проверки в отдельные функции:

const isValidName = (name) =>
  validator.isLength(name || "", { min: 2, max: 50 });

const isValidAge = (age) =>
  validator.isInt(String(age), { min: 0, max: 120 });

Использование композиции:

function validateProfile(profile) {
  const errors = {};

  if (!isValidName(profile.name)) {
    errors.name = "Некорректное имя";
  }

  if (!isValidAge(profile.age)) {
    errors.age = "Некорректный возраст";
  }

  return {
    isValid: Object.keys(errors).length === 0,
    errors
  };
}

Композиционный подход уменьшает связность и упрощает поддержку логики.


Обработка динамических структур

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

function validateObject(obj, schema) {
  const errors = {};

  Object.keys(schema).forEach((key) => {
    const rule = schema[key];
    const value = obj[key];

    if (rule.required && validator.isEmpty(String(value || ""))) {
      errors[key] = "Поле обязательно";
      return;
    }

    if (rule.type === "email" && !validator.isEmail(String(value || ""))) {
      errors[key] = "Некорректный email";
    }

    if (rule.type === "int" && !validator.isInt(String(value || ""))) {
      errors[key] = "Некорректное число";
    }
  });

  return errors;
}

Пример схемы:

const schema = {
  email: { required: true, type: "email" },
  age: { required: true, type: "int" }
};

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


Объединение результатов валидации

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

function aggregateValidation(...results) {
  const errors = {};

  results.forEach((result) => {
    Object.assign(errors, result);
  });

  return {
    isValid: Object.keys(errors).length === 0,
    errors
  };
}

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


Особенности обработки граничных случаев

Сложные структуры часто содержат:

  • null и undefined
  • смешанные типы (string | number)
  • частично заполненные объекты
  • устаревшие поля

Защитная проверка становится обязательной частью каждого валидатора:

const safeString = (value) => (value == null ? "" : String(value));

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


Интеграция с бизнес-логикой

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

  • роли пользователя
  • состояние сущности
  • контекст операции
  • источник данных

Validator.js в такой архитектуре выступает как слой примитивных предикатов, поверх которых строится доменная логика валидации, организованная через функции, композиции и рекурсию.