Валидация JSON payload

JSON payload — это структура данных, передаваемая между клиентом и сервером в формате JSON. Чаще всего payload содержится в HTTP-запросах:

{
  "email": "admin@example.com",
  "password": "123456",
  "age": 25
}

Основная задача валидации — проверить:

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

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

Установка Validator.js

Установка через npm

npm install validator

Подключение

CommonJS:

const validator = require('validator');

ES Modules:

import validator from 'validator';

Особенности Validator.js

Validator.js работает преимущественно со строками.

Например:

validator.isEmail('admin@example.com');

Но:

validator.isEmail(12345);

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

Поэтому при валидации JSON payload почти всегда требуется:

  • предварительная проверка типов;
  • приведение значений;
  • защита от undefined и null.

Типичная структура JSON payload

Пример запроса регистрации пользователя:

{
  "name": "Alex",
  "email": "alex@example.com",
  "password": "StrongPass123",
  "age": 30,
  "website": "https://example.com"
}

Проверка должна удостовериться, что:

Поле Проверка
name не пустое
email корректный email
password минимальная длина
age число в допустимом диапазоне
website корректный URL

Базовая ручная валидация payload

Простая функция валидации

import validator from 'validator';

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

  if (!data.name || validator.isEmpty(data.name)) {
    errors.name = 'Name is required';
  }

  if (!data.email || !validator.isEmail(data.email)) {
    errors.email = 'Invalid email';
  }

  if (!data.password || !validator.isLength(data.password, { min: 8 })) {
    errors.password = 'Password too short';
  }

  return errors;
}

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

const payload = {
  name: 'Alex',
  email: 'wrong-email',
  password: '123'
};

const errors = validateUser(payload);

console.log(errors);

Результат:

{
  email: 'Invalid email',
  password: 'Password too short'
}

Проверка обязательных полей

Проверка на существование

if (!data.email)

Недостаток такого подхода:

0
false
''
null
undefined

все считаются ложными значениями.

Поэтому лучше выполнять более точные проверки.

Проверка undefined и null

if (data.email === undefined || data.email === null)

Проверка пустой строки

validator.isEmpty(data.email);

Важно учитывать:

validator.isEmpty('   ');

вернёт false, поскольку строка содержит пробелы.

Удаление пробелов

validator.isEmpty(data.email.trim());

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

Проблема отсутствующих полей

Payload:

{
  "email": null
}

Вызов:

validator.isEmail(null);

приведёт к ошибке.

Безопасная нормализация

function normalize(value) {
  if (value === undefined || value === null) {
    return '';
  }

  return String(value);
}

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

const email = normalize(data.email);

if (!validator.isEmail(email)) {
  errors.email = 'Invalid email';
}

Валидация email

Базовая проверка

validator.isEmail('admin@example.com');

Результат:

true

Проверка payload

if (!validator.isEmail(normalize(data.email))) {
  errors.email = 'Invalid email';
}

Дополнительные настройки

validator.isEmail(email, {
  allow_utf8_local_part: false,
  require_tld: true
});

Основные параметры

Параметр Назначение
require_tld требует доменную зону
allow_ip_domain разрешает IP вместо домена
allow_utf8_local_part UTF-8 символы
ignore_max_length игнорирует ограничение длины

Валидация URL

Проверка сайта

validator.isURL('https://example.com');

Проверка в payload

if (!validator.isURL(normalize(data.website))) {
  errors.website = 'Invalid URL';
}

Проверка только HTTPS

validator.isURL(url, {
  protocols: ['https'],
  require_protocol: true
});

Валидация чисел

Validator.js работает со строками.

Проверка целого числа

validator.isInt('25');

Проверка диапазона

validator.isInt('25', {
  min: 18,
  max: 60
});

Проверка возраста

const age = normalize(data.age);

if (!validator.isInt(age, { min: 18, max: 120 })) {
  errors.age = 'Invalid age';
}

Валидация decimal и float

Проверка дробного числа

validator.isFloat('19.99');

Ограничение диапазона

validator.isFloat('19.99', {
  min: 0,
  max: 1000
});

Проверка цены

const price = normalize(data.price);

if (!validator.isFloat(price, { min: 0 })) {
  errors.price = 'Invalid price';
}

Проверка строк

Минимальная и максимальная длина

validator.isLength('password123', {
  min: 8,
  max: 32
});

Проверка имени пользователя

const username = normalize(data.username);

if (!validator.isLength(username, { min: 3, max: 20 })) {
  errors.username = 'Username length invalid';
}

Проверка только букв и цифр

Alphanumeric

validator.isAlphanumeric('alex123');

Проверка логина

if (!validator.isAlphanumeric(username)) {
  errors.username = 'Only letters and numbers allowed';
}

Проверка UUID

UUID v4

validator.isUUID(id, 4);

Проверка идентификатора

const userId = normalize(data.userId);

if (!validator.isUUID(userId)) {
  errors.userId = 'Invalid UUID';
}

Проверка JSON внутри payload

Иногда payload содержит JSON-строку.

Пример

{
  "settings": "{\"theme\":\"dark\"}"
}

Проверка

validator.isJSON(data.settings);

Использование

if (!validator.isJSON(normalize(data.settings))) {
  errors.settings = 'Invalid JSON';
}

Проверка даты

ISO8601

validator.isISO8601('2025-01-01');

Проверка даты рождения

const birthDate = normalize(data.birthDate);

if (!validator.isISO8601(birthDate)) {
  errors.birthDate = 'Invalid date';
}

Проверка телефона

Mobile phone

validator.isMobilePhone('+77001234567', 'any');

Проверка номера

const phone = normalize(data.phone);

if (!validator.isMobilePhone(phone, 'any')) {
  errors.phone = 'Invalid phone number';
}

Проверка по локали

validator.isMobilePhone(phone, 'kk-KZ');

Санитизация payload

Validator.js содержит не только валидаторы, но и санитайзеры.

Trim

Удаление пробелов:

validator.trim('   admin@example.com   ');

Escape

Экранирование HTML:

validator.escape('<script>alert(1)</script>');

Результат:

&lt;script&gt;alert(1)&lt;/script&gt;

Normalize email

validator.normalizeEmail('ADMIN@EXAMPLE.COM');

Полная обработка payload

Подготовка данных

function sanitizeUserPayload(data) {
  return {
    name: validator.trim(normalize(data.name)),
    email: validator.normalizeEmail(normalize(data.email)),
    website: validator.trim(normalize(data.website))
  };
}

Комплексная валидация payload

Полный пример

import validator from 'validator';

function normalize(value) {
  if (value === undefined || value === null) {
    return '';
  }

  return String(value);
}

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

  const name = validator.trim(normalize(data.name));
  const email = validator.normalizeEmail(normalize(data.email));
  const password = normalize(data.password);
  const age = normalize(data.age);

  if (validator.isEmpty(name)) {
    errors.name = 'Name required';
  }

  if (!validator.isEmail(email || '')) {
    errors.email = 'Invalid email';
  }

  if (!validator.isLength(password, { min: 8 })) {
    errors.password = 'Password too short';
  }

  if (!validator.isInt(age, { min: 18, max: 120 })) {
    errors.age = 'Invalid age';
  }

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

Валидация массива объектов

Payload

{
  "users": [
    {
      "email": "admin@example.com"
    },
    {
      "email": "wrong-email"
    }
  ]
}

Проверка

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

  users.forEach((user, index) => {
    if (!validator.isEmail(normalize(user.email))) {
      errors.push({
        index,
        field: 'email',
        message: 'Invalid email'
      });
    }
  });

  return errors;
}

Валидация вложенных объектов

Payload

{
  "profile": {
    "contacts": {
      "email": "admin@example.com"
    }
  }
}

Проверка

const email = normalize(
  data.profile?.contacts?.email
);

if (!validator.isEmail(email)) {
  errors.email = 'Invalid email';
}

Валидация в Express.js

Middleware

import validator from 'validator';

function validateRegister(req, res, next) {
  const errors = {};

  const email = normalize(req.body.email);
  const password = normalize(req.body.password);

  if (!validator.isEmail(email)) {
    errors.email = 'Invalid email';
  }

  if (!validator.isLength(password, { min: 8 })) {
    errors.password = 'Password too short';
  }

  if (Object.keys(errors).length > 0) {
    return res.status(400).json({
      errors
    });
  }

  next();
}

Подключение

app.post('/register', validateRegister, controller);

Формирование структуры ошибок

Простая структура

{
  email: 'Invalid email'
}

Расширенная структура

{
  email: {
    code: 'INVALID_EMAIL',
    message: 'Email format invalid'
  }
}

Массив ошибок

[
  {
    field: 'email',
    message: 'Invalid email'
  }
]

Централизация валидаторов

Создание helper-функций

function isRequired(value) {
  return !validator.isEmpty(
    normalize(value).trim()
  );
}
function isValidEmail(value) {
  return validator.isEmail(
    normalize(value)
  );
}

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

if (!isRequired(data.email)) {
  errors.email = 'Required';
}

Асинхронная валидация

Validator.js выполняет только синхронные проверки.

Но часто требуется:

  • проверка уникальности email;
  • существование пользователя;
  • проверка записи в БД.

Комбинирование с async/await

async function validate(data) {
  const errors = {};

  if (!validator.isEmail(normalize(data.email))) {
    errors.email = 'Invalid email';
  }

  const exists = await User.exists({
    email: data.email
  });

  if (exists) {
    errors.email = 'Email already used';
  }

  return errors;
}

Типичные ошибки

Передача non-string значений

Ошибка:

validator.isEmail(undefined);

Решение:

normalize(value);

Отсутствие trim

Ошибка:

validator.isEmail('   admin@example.com   ');

Результат:

false

Правильно:

validator.isEmail(
  validator.trim(email)
);

Слепое доверие frontend

Даже если frontend уже проверяет данные:

<input type="email">

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


Проверка только формата

Наличие корректного email не означает существование адреса.

test@test.test

формально валиден.


Стратегии организации валидации

Подход «одна функция — одна сущность»

validateUserPayload()
validateProductPayload()
validateOrderPayload()

Преимущества:

  • простота поддержки;
  • переиспользование;
  • читаемость.

Разделение этапов

1. Санитизация

trim()
escape()
normalizeEmail()

2. Валидация

isEmail()
isInt()
isURL()

3. Формирование ошибок

errors.email = 'Invalid email';

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

Validator.js работает быстро, поскольку:

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

Однако при больших payload:

{
  "users": [...]
}

важно:

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

Ограничение неизвестных полей

Payload

{
  "email": "admin@example.com",
  "role": "admin",
  "isRoot": true
}

Иногда необходимо запрещать лишние поля.

Проверка whitelist

const allowedFields = [
  'email',
  'password'
];

const unknownFields = Object.keys(data)
  .filter(key => !allowedFields.includes(key));

if (unknownFields.length > 0) {
  errors.fields = 'Unknown fields detected';
}

Валидация паролей

Проверка сложности

const password = normalize(data.password);

const strong =
  validator.isLength(password, { min: 8 }) &&
  validator.matches(password, /[A-Z]/) &&
  validator.matches(password, /[0-9]/);

if (!strong) {
  errors.password = 'Weak password';
}

Проверка slug

Пример

validator.isSlug('my-awesome-article');

Проверка hash

MD5

validator.isHash(hash, 'md5');

SHA256

validator.isHash(hash, 'sha256');

Проверка MIME type

validator.isMimeType('application/json');

Проверка IP-адресов

IPv4

validator.isIP('192.168.0.1', 4);

IPv6

validator.isIP('2001:db8::1', 6);

Создание универсального валидатора

Конфигурационный подход

const schema = {
  email: value =>
    validator.isEmail(normalize(value)),

  age: value =>
    validator.isInt(normalize(value), {
      min: 18
    })
};

Универсальная функция

function validate(schema, data) {
  const errors = {};

  for (const field in schema) {
    const valid = schema[field](data[field]);

    if (!valid) {
      errors[field] = 'Invalid value';
    }
  }

  return errors;
}

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

const errors = validate(schema, payload);

Когда Validator.js недостаточно

Validator.js отлично подходит для:

  • небольших API;
  • middleware;
  • базовой проверки payload;
  • санитизации строк.

Но в крупных проектах часто требуются:

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

В таких случаях Validator.js обычно комбинируют с:

  • Joi;
  • Yup;
  • Zod;
  • Ajv;
  • express-validator.

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