Пакетная обработка данных

validator.js изначально ориентирован на проверку отдельных строковых значений, однако в реальных приложениях почти всегда возникает необходимость работать с наборами данных: списками пользователей, массивами форм, пакетами импортируемых записей, результатами API или потоками данных из внешних систем. Пакетная обработка в этом контексте означает применение набора правил валидации к коллекциям значений с последующей агрегацией результатов, оптимизацией повторного использования проверок и контролем производительности при больших объёмах входных данных.


Базовая модель пакетной валидации

На уровне архитектуры пакетная обработка строится вокруг трёх операций:

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

Типовая структура:

import validator from "validator";

function validateItem(item) {
  const errors = [];

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

  if (!validator.isLength(item.password, { min: 8 })) {
    errors.push("Пароль слишком короткий");
  }

  return errors;
}

function validateBatch(data) {
  return data.map((item, index) => ({
    index,
    errors: validateItem(item)
  }));
}

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


Валидация массивов однотипных значений

Наиболее частый случай — проверка массива строк или чисел:

function validateEmails(emails) {
  return emails.map(email => ({
    value: email,
    valid: validator.isEmail(email)
  }));
}

При работе с большими массивами важно избегать лишних преобразований данных внутри цикла. Например, нормализация строк (trim, lowercase) должна выполняться один раз до валидации:

const normalized = emails.map(e => e.trim().toLowerCase());

Пакетная обработка объектов сложной структуры

Реальные системы чаще оперируют не примитивами, а объектами:

const users = [
  { email: "test@mail.com", age: "25" },
  { email: "invalid@", age: "not-a-number" }
];

В этом случае формируется единый слой правил:

function validateUser(user) {
  const errors = [];

  if (!validator.isEmail(user.email)) {
    errors.push("email");
  }

  if (!validator.isInt(user.age, { min: 0 })) {
    errors.push("age");
  }

  return errors;
}

Пакетная обработка:

function validateUsers(users) {
  const result = [];

  for (let i = 0; i < users.length; i++) {
    const errors = validateUser(users[i]);

    result.push({
      index: i,
      valid: errors.length === 0,
      errors
    });
  }

  return result;
}

Агрегация ошибок в пакетной обработке

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

1. Индексная агрегация

Ошибки привязываются к индексу элемента:

{
  index: 3,
  errors: ["email", "password"]
}

2. Ключевая агрегация

Ошибки группируются по идентификатору:

{
  id: "user_42",
  errors: ["email"]
}

3. Полевая агрегация

Ошибки структурируются по полям:

{
  email: "invalid",
  password: "too short"
}

Последний вариант наиболее удобен для UI-слоя и API-ответов.


Композиция валидаторов для пакетной обработки

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

function composeValidators(value, rules) {
  const errors = [];

  if (rules.email && !validator.isEmail(value.email)) {
    errors.push("email");
  }

  if (rules.minLength && !validator.isLength(value.name, { min: rules.minLength })) {
    errors.push("name");
  }

  return errors;
}

Пакетная обработка:

function validateBatch(data, rules) {
  return data.map(item => composeValidators(item, rules));
}

Повторное использование валидаторов

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

const isValidEmail = validator.isEmail;
const isValidInt = validator.isInt;

function validate(item) {
  const errors = [];

  if (!isValidEmail(item.email)) errors.push("email");
  if (!isValidInt(item.age)) errors.push("age");

  return errors;
}

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


Потоковая пакетная обработка

При обработке больших массивов (десятки тысяч записей и более) использование map может приводить к росту потребления памяти. Альтернативой становится потоковая обработка:

function validateStream(data) {
  const result = [];

  for (const item of data) {
    result.push({
      valid: validateUser(item).length === 0
    });
  }

  return result;
}

При необходимости можно добавлять ранний выход:

if (errors.length > 1000) break;

Частичная валидация пакетов

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

  • проверка первых N элементов;
  • выборочная проверка по шагу;
  • случайная выборка.

Пример выборочной проверки:

function validateSample(data, step = 10) {
  const result = [];

  for (let i = 0; i < data.length; i += step) {
    result.push(validateUser(data[i]));
  }

  return result;
}

Оптимизация пакетной обработки

Основные факторы производительности:

Уменьшение количества вызовов валидаторов

Каждый вызов validator.isEmail или validator.isLength имеет накладные расходы. При пакетной обработке они умножаются на размер коллекции.

Предварительная нормализация данных

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

Кэширование результатов

Если одни и те же значения встречаются многократно:

const cache = new Map();

function cachedIsEmail(value) {
  if (cache.has(value)) return cache.get(value);

  const result = validator.isEmail(value);
  cache.set(value, result);

  return result;
}

Пакетная валидация в гибридных структурах

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

function normalize(item) {
  return {
    email: String(item.email || "").trim(),
    age: String(item.age || "")
  };
}

function validateBatch(data) {
  return data.map(item => validateUser(normalize(item)));
}

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

После завершения проверки формируется итоговая структура:

{
  total: 1000,
  valid: 850,
  invalid: 150,
  errors: [...]
}

Агрегация выполняется отдельно от самой валидации:

function summarize(results) {
  return results.reduce(
    (acc, item) => {
      item.valid ? acc.valid++ : acc.invalid++;
      return acc;
    },
    { valid: 0, invalid: 0 }
  );
}

Детерминированность и воспроизводимость

Пакетная обработка требует стабильного поведения функций валидации. Любые побочные эффекты (изменение состояния, зависимость от времени, внешних источников) приводят к несогласованным результатам при повторных запусках.

Поэтому валидаторы должны оставаться чистыми функциями:

function isValid(item) {
  return validator.isEmail(item.email);
}

Расширяемые схемы пакетной проверки

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

const schema = {
  email: validator.isEmail,
  age: value => validator.isInt(value, { min: 0 })
};

function validateBySchema(item, schema) {
  const errors = [];

  for (const key in schema) {
    if (!schema[key](item[key])) {
      errors.push(key);
    }
  }

  return errors;
}

Масштабирование пакетной обработки

При росте объёмов данных применяются дополнительные уровни:

  • параллельная обработка (Worker Threads);
  • разбиение на чанки;
  • очереди задач.

Пример чанкинга:

function chunk(array, size) {
  const result = [];

  for (let i = 0; i < array.length; i += size) {
    result.push(array.slice(i, i + size));
  }

  return result;
}

Контроль целостности пакетов

В системах с критичными данными применяется принцип атомарности пакета: либо весь пакет валиден, либо отклоняется целиком. Для этого вводится предварительный проход:

const invalid = batch.some(item => validateUser(item).length > 0);

Если обнаружены ошибки, дальнейшая обработка прекращается.