@IsByteLength

Декоратор @IsByteLength в class-validator предназначен для проверки длины строки в байтах, а не в символах. Это принципиально важно в контексте UTF-8, где один символ может занимать от 1 до 4 байт, что делает стандартные проверки длины недостаточными для задач, связанных с хранением, сетевыми протоколами или ограничениями баз данных.

В отличие от @Length, который измеряет количество символов, @IsByteLength вычисляет фактический размер строки в памяти после UTF-8 кодирования.

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

  • ASCII символы: 1 байт
  • Кириллические символы: обычно 2 байта
  • Emoji и редкие символы: 3–4 байта

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

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

import { IsByteLength } from 'class-validator';

class CreateUserDto {
  @IsByteLength(10, 50)
  username: string;
}

Здесь поле username должно занимать от 10 до 50 байт в UTF-8 представлении.

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

@IsByteLength принимает следующие аргументы:

  • min: минимальное количество байт
  • max: максимальное количество байт
  • options: объект конфигурации (опционально)
@IsByteLength(5, 20, {
  message: 'Длина строки должна быть от 5 до 20 байт',
})
field: string;

Разница между @IsLength и @IsByteLength

Ключевое различие заключается в единице измерения:

Декоратор Единица измерения Пример поведения
@IsLength символы “?” = 1 символ
@IsByteLength байты “?” = 4 байта

Это различие критично при работе с ограничениями на уровне:

  • SQL VARCHAR(n) в байтах (в некоторых СУБД)
  • сетевых протоколов
  • API-шлюзов
  • систем хранения с фиксированными лимитами

Поведение с Unicode и многобайтовыми символами

UTF-8 кодирование приводит к значительным различиям между «видимой» и фактической длиной строки.

Пример:

const value = "Привет"; 

Хотя строка содержит 6 символов, в UTF-8 она занимает больше 6 байт из-за кириллицы.

Другой пример:

const value = "??";

Каждый emoji занимает 4 байта, следовательно строка из двух emoji уже занимает 8 байт.

Практическое применение

Ограничения баз данных

Во многих схемах данных поле может быть ограничено по байтам:

@IsByteLength(0, 255)
title: string;

Это часто используется, когда:

  • индекс поля зависит от байтовой длины
  • используется устаревшая кодировка или ограничения ORM
  • требуется совместимость с внешними системами

Валидация API запросов

При приёме данных через HTTP API важно гарантировать, что payload не превысит допустимый размер после сериализации:

class UpdateProfileDto {
  @IsByteLength(0, 100)
  displayName: string;
}

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

Поведение при некорректных типах

Декоратор применяется только к строкам. Если значение не является строкой, валидация будет провалена (если не отключены соответствующие преобразования).

@IsByteLength(1, 10)
value: string;

Примеры невалидных значений:

  • number
  • boolean
  • object
  • null

Пользовательские сообщения об ошибках

Можно задавать динамические сообщения:

@IsByteLength(3, 15, {
  message: 'Поле должно содержать от 3 до 15 байт',
})
nickname: string;

Также доступна интерполяция контекста:

@IsByteLength(5, 20, {
  message: ({ constraints }) =>
    `Допустимая длина: ${constraints[0]}–${constraints[1]} байт`,
})
field: string;

Влияние нормализации строки

В JavaScript строки хранятся в UTF-16, но проверка байтовой длины происходит после преобразования в UTF-8. Это означает, что:

  • комбинированные символы могут увеличивать размер
  • нормализация Unicode (NFC/NFD) может влиять на итоговую длину
  • визуально идентичные строки могут иметь разный байтовый размер

Пример:

const a = "é";      // один символ
const b = "é";     // e + комбинирующий акцент

Обе строки выглядят одинаково, но их байтовая структура различается.

Ограничения и особенности реализации

  1. Поддержка зависит от механизма преобразования строки в байты UTF-8
  2. Проверка выполняется синхронно
  3. Не учитывает «логические символы», только байты
  4. Может давать неожиданные результаты при работе с редкими Unicode-символами

Типичные ошибки при использовании

Частая ошибка — ожидание совпадения с количеством символов:

@IsByteLength(0, 10)
text: string;

Разработчик может предполагать, что допустимо 10 символов, но фактически:

  • 10 ASCII символов допустимы
  • 10 кириллических символов могут уже превышать лимит
  • 3–4 emoji гарантированно превысят ограничение

Сочетание с другими валидаторами

@IsByteLength часто используется совместно:

import { IsString, IsNotEmpty, IsByteLength } from 'class-validator';

class MessageDto {
  @IsString()
  @IsNotEmpty()
  @IsByteLength(1, 200)
  content: string;
}

Такой подход разделяет ответственность:

  • @IsString — тип
  • @IsNotEmpty — наличие данных
  • @IsByteLength — технические ограничения хранения

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

При использовании class-transformer возможны ситуации, когда входные данные приводятся к строке автоматически. Это может изменить результат проверки:

@IsByteLength(2, 10)
value: string;

Если вход:

{ "value": 12345 }

После преобразования в строку "12345" проверка будет выполнена уже по строковому представлению.

Использование в системах с жёсткими лимитами

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

  • бинарные протоколы
  • embedded-системы
  • старые SQL схемы с BYTE-лимитами
  • межсистемные интеграции

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