Декоратор @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;
}
Здесь происходит два этапа:
@Type(() => Number) преобразует строку в число@IsNumber() проверяет результатДекоратор поддерживает конфигурационный объект, который управляет строгими аспектами проверки.
@IsNumber({}, { allowNaN: true })
value: number;
Разрешает значение NaN.
По умолчанию NaN считается недопустимым, так как:
Использование allowNaN встречается редко и обычно
ограничено внутренними вычислительными системами.
@IsNumber({}, { allowInfinity: true })
value: number;
Разрешает:
Infinity-InfinityБез этой опции такие значения считаются невалидными.
Практическое применение ограничено, так как бесконечность редко имеет смысл в доменных моделях (например, цена, возраст, количество).
@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"
}
Результат:
Это ключевой момент, который часто приводит к ошибочному восприятию работы class-validator.
В экосистеме 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:
Без него валидатор работает строго по фактическому типу входного значения.
В 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)Number("abc") // NaN
Без allowNaN такое значение считается невалидным.
Это защищает:
1 / 0 // Infinity
В большинстве доменных моделей это недопустимо значение.
JavaScript ограничен Number.MAX_SAFE_INTEGER:
9007199254740991 // безопасное значение
@IsNumber не проверяет безопасность диапазона автоматически, поэтому при необходимости используется дополнительная логика:
import { IsNumber, Min, Max } from 'class-validator';
class ScoreDto {
@IsNumber()
@Min(0)
@Max(100)
score: number;
}
@IsNumber отвечает за тип, @Min/@Max — за диапазон.
@IsOptional()
@IsNumber()
value?: number;
Позволяет пропускать значение undefined.
Различия:
undefined — обычно пропускается через @IsOptionalnull — считается значением и валидируетсяПример:
class ExampleDto {
@IsNumber()
value: number;
}
{
"value": null
}
Результат: ошибка валидации
Если необходимо разрешить null, используется кастомная логика или отдельная настройка.
Наиболее распространённая проблема:
@IsNumber()
value: number;
При входе "123" валидатор не пропустит значение.
class-validator сам по себе не преобразует типы. Это роль class-transformer.
Без ограничения точности возможны:
@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 обычно участвует в моделях:
Типичная схема:
В системах с жёсткой типизацией:
transform: true@Type(() => Number)Это позволяет обеспечить предсказуемость данных на входе и исключить скрытые ошибки вычислений
Использование @IsNumber дисциплинирует структуру API:
Модель данных становится более детерминированной, так как каждое числовое поле проходит строгую проверку до попадания в бизнес-логику