@IsNumber

Декоратор @IsNumber в библиотеке class-validator используется для строгой проверки того, что значение свойства является числом. Он относится к базовым числовым валидаторам и применяется как в чистых JavaScript/TypeScript классах, так и в рамках фреймворков, таких как NestJS.

Проверка числа в JavaScript требует учитывать особенности языка: автоматическое приведение типов, наличие NaN, Infinity, чисел в строковом формате и плавающей точности. @IsNumber решает задачу валидации на уровне бизнес-логики, исключая некорректные значения до попадания в обработку.


Базовая семантика проверки числа

Поведение @IsNumber заключается в проверке типа значения:

  • значение должно быть типа number
  • при необходимости дополнительно проверяются ограничения на формат числа
  • поведение можно настраивать через параметры

Важно учитывать, что в Jav * aScript:

  • "123" — строка, не число
  • 123 — число
  • NaN — число с особым значением
  • Infinity и -Infinity — числовые значения, но редко допустимы в бизнес-логике

Именно поэтому @IsNumber почти всегда используется с опциями, уточняющими допустимый диапазон значений.


Синтаксис декоратора

Основная форма использования:

import { IsNumber } from 'class-validator';

class CreateProductDto {
  @IsNumber()
  price: number;
}

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


Типичная проблема: строковые числа

В реальных API данные часто приходят как строки:

{
  "price": "100"
}

Без дополнительной обработки @IsNumber не пропустит такое значение.

Для решения этой проблемы используется комбинация с преобразованием типов через class-transformer:

import { Type } from 'class-transformer';
import { IsNumber } from 'class-validator';

class CreateProductDto {
  @Type(() => Number)
  @IsNumber()
  price: number;
}

Здесь происходит два этапа:

  1. @Type(() => Number) преобразует строку в число
  2. @IsNumber() проверяет результат

Опции @IsNumber

Декоратор поддерживает конфигурационный объект, который управляет строгими аспектами проверки.

allowNaN

@IsNumber({}, { allowNaN: true })
value: number;

Разрешает значение NaN.

По умолчанию NaN считается недопустимым, так как:

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

Использование allowNaN встречается редко и обычно ограничено внутренними вычислительными системами.


allowInfinity

@IsNumber({}, { allowInfinity: true })
value: number;

Разрешает:

  • Infinity
  • -Infinity

Без этой опции такие значения считаются невалидными.

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


maxDecimalPlaces

@IsNumber({}, { maxDecimalPlaces: 2 })
price: number;

Ограничивает количество знаков после запятой.

Примеры:

  • 12.34 → валидно
  • 12.345 → невалидно при maxDecimalPlaces: 2

Это особенно важно для:

  • финансовых данных
  • валютных значений
  • измерений с фиксированной точностью

Строгий и нестрогий режим

В @IsNumber существует различие между строгой и нестрогой проверкой, которое определяется первым аргументом:

@IsNumber({ maxDecimalPlaces: 2 }, { allowNaN: false })
value: number;

Первый параметр — объект дополнительных правил проверки.

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


Поведение при отсутствии преобразования типов

Если входные данные не преобразуются, возникает типичная ошибка:

class ExampleDto {
  @IsNumber()
  value: number;
}

Запрос:

{
  "value": "10"
}

Результат:

  • валидация провалится
  • причина — тип string

Это ключевой момент, который часто приводит к ошибочному восприятию работы class-validator.


Интеграция с class-transformer

В экосистеме class-validator почти всегда используется совместно с class-transformer:

import { Type } from 'class-transformer';
import { IsNumber } from 'class-validator';

class PaymentDto {
  @Type(() => Number)
  @IsNumber({ maxDecimalPlaces: 2 })
  amount: number;
}

Роль class-transformer:

  • преобразует входные данные (JSON → классы)
  • обеспечивает корректный тип для валидатора

Без него валидатор работает строго по фактическому типу входного значения.


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

В NestJS @IsNumber часто работает внутри ValidationPipe:

app.useGlobalPipes(
  new ValidationPipe({
    transform: true,
    whitelist: true,
  }),
);

При transform: true происходит автоматическое приведение типов, если используется @Type.

Комбинация выглядит следующим образом:

class UserDto {
  @Type(() => Number)
  @IsNumber({ maxDecimalPlaces: 0 })
  age: number;
}

Поведение:

  • вход "25" → число 25
  • проверка проходит
  • вход "25.5" → ошибка (из-за maxDecimalPlaces: 0)

Обработка крайних значений

NaN

Number("abc") // NaN

Без allowNaN такое значение считается невалидным.

Это защищает:

  • вычисления
  • фильтрацию данных
  • агрегации

Infinity

1 / 0 // Infinity

В большинстве доменных моделей это недопустимо значение.


Очень большие числа

JavaScript ограничен Number.MAX_SAFE_INTEGER:

9007199254740991 // безопасное значение

@IsNumber не проверяет безопасность диапазона автоматически, поэтому при необходимости используется дополнительная логика:

  • кастомные валидаторы
  • проверки диапазона через @Min / @Max

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

@Min и @Max

import { IsNumber, Min, Max } from 'class-validator';

class ScoreDto {
  @IsNumber()
  @Min(0)
  @Max(100)
  score: number;
}

@IsNumber отвечает за тип, @Min/@Max — за диапазон.


@IsOptional

@IsOptional()
@IsNumber()
value?: number;

Позволяет пропускать значение undefined.


Поведение при null и undefined

Различия:

  • undefined — обычно пропускается через @IsOptional
  • null — считается значением и валидируется

Пример:

class ExampleDto {
  @IsNumber()
  value: number;
}
{
  "value": null
}

Результат: ошибка валидации

Если необходимо разрешить null, используется кастомная логика или отдельная настройка.


Частые ошибки использования

1. Отсутствие @Type

Наиболее распространённая проблема:

@IsNumber()
value: number;

При входе "123" валидатор не пропустит значение.


2. Ожидание автоматического преобразования

class-validator сам по себе не преобразует типы. Это роль class-transformer.


3. Использование для финансов без maxDecimalPlaces

Без ограничения точности возможны:

  • накопление ошибок округления
  • неконсистентные значения

4. Попытка использовать для bigint

@IsNumber не предназначен для bigint.

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


Кастомные сценарии расширения

Иногда стандартных возможностей недостаточно. Тогда создаётся кастомный валидатор:

import {
  ValidatorConstraint,
  ValidatorConstraintInterface,
} from 'class-validator';

@ValidatorConstraint({ name: 'isStrictPositiveNumber', async: false })
export class IsStrictPositiveNumber implements ValidatorConstraintInterface {
  validate(value: any) {
    return typeof value === 'number' && value > 0 && Number.isFinite(value);
  }
}

Такой подход используется для:

  • бизнес-ограничений
  • доменных правил
  • финансовых моделей

Производственные практики применения

В реальных приложениях @IsNumber обычно участвует в моделях:

  • DTO для API
  • формы ввода данных
  • сервисные слои валидации
  • интеграции с внешними системами

Типичная схема:

  1. получение JSON
  2. трансформация в DTO
  3. проверка @IsNumber
  4. бизнес-обработка

Особенности поведения в строгих API

В системах с жёсткой типизацией:

  • включают transform: true
  • используют @Type(() => Number)
  • ограничивают decimal places
  • запрещают NaN и Infinity

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


Влияние на архитектуру данных

Использование @IsNumber дисциплинирует структуру API:

  • устраняет неявные строки вместо чисел
  • фиксирует формат числовых значений
  • упрощает сериализацию и десериализацию
  • снижает количество runtime-ошибок

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