Декоратор @IsDecimal в библиотеке
class-validator предназначен для проверки строкового
представления десятичных чисел. Его ключевая особенность заключается в
том, что он работает именно с строками, а не с
числовыми типами JavaScript, и проверяет соответствие строгому формату
десятичного числа с заданной конфигурацией точности.
@IsDecimal используется для валидации значений, которые
должны быть представлены в виде десятичной записи:
Ключевой принцип работы: значение должно быть строкой, соответствующей формату decimal, а не числом JavaScript.
Декоратор проверяет:
1e10 не
допускается)Декоратор имеет следующий базовый вид:
@IsDecimal(options?: IsDecimalOptions)
Основные параметры:
interface IsDecimalOptions {
decimal_digits?: string; // допустимое количество знаков после разделителя
force_decimal?: boolean; // обязательное наличие дробной части
locale?: string; // локаль (например, 'en-US', 'de-DE')
}
Определяет допустимое количество знаков после десятичного разделителя.
Примеры:
"2" → строго 2 знака после точки"1,3" → от 1 до 3 знаков"*"→ любое количество знаковПример:
import { IsDecimal } from 'class-validator';
class ProductDto {
@IsDecimal({ decimal_digits: '2' })
price: string;
}
Допустимо:
"10.50""0.99"Недопустимо:
"10.5""10.500"Требует обязательного наличия дробной части.
@IsDecimal({ force_decimal: true })
value: string;
Поведение:
Допустимо:
"10.0""3.14"Недопустимо:
"10""5"Определяет формат десятичного разделителя:
"en-US" → точка (.)"de-DE" → запятая (,)Пример:
@IsDecimal({ locale: 'de-DE' })
value: string;
Допустимо:
"10,5"Недопустимо:
"10.5"@IsDecimal не приводит тип автоматически:
@IsDecimal()
value: string;
Если передать число:
value = 10.5;
валидация будет провалена, поскольку ожидается строка
"10.5".
Следующие значения считаются невалидными:
"1e5""2E10"Даже если они корректны математически, формат не соответствует decimal-строке.
Отрицательные значения допускаются, если знак минуса используется корректно:
Допустимо:
"-10.5"Недопустимо:
"--10.5""10.-5"@IsNumber работает с типом number:
@IsNumber()
value: number;
Особенности:
10.5 как число1e5@IsNumberString проверяет, что строка является числом,
но:
Фокусируется на:
class PaymentDto {
@IsDecimal({ decimal_digits: '2', force_decimal: true })
amount: string;
}
Пример:
"199.99" — корректно"199.9" — некорректно"199" — некорректноclass DiscountDto {
@IsDecimal({ decimal_digits: '1,2' })
percent: string;
}
Допустимо:
"10.5""7.25"class LocationDto {
@IsDecimal({ decimal_digits: '*' })
latitude: string;
@IsDecimal({ decimal_digits: '*' })
longitude: string;
}
Частая проблема — несовпадение типов при получении данных из HTTP-запросов.
{
price: 10.5
}
валидация @IsDecimal() не пройдет.
import { Transform } from 'class-transformer';
class ProductDto {
@Transform(({ value }) => value?.toString())
@IsDecimal({ decimal_digits: '2' })
price: string;
}
Теперь:
10.50 → "10.5" (после преобразования)"10.50" → "10.50"Допустимые значения:
"0.5""00.50" (зависит от реализации и строгих режимов)Однако бизнес-логика часто требует дополнительной нормализации, так
как class-validator не всегда интерпретирует формат как
семантически корректный, а лишь синтаксически допустимый.
@IsDecimal()
price: number; // ошибка архитектуры валидации
При работе с HTTP payload:
price: 12.50
валидация провалится без приведения к строке.
@IsDecimal({ decimal_digits: '2' })
и вход:
"10.5" → ошибка"10.50" → корректноРазделитель играет ключевую роль.
@IsDecimal({ locale: 'en-US' })
"10.5""10,5"@IsDecimal({ locale: 'de-DE' })
"10,5""10.5"@IsDecimal не заменяет:
@IsOptional@IsNotEmptyКомбинации:
@IsOptional()
@IsDecimal()
value?: string;
@IsDecimal(
{ decimal_digits: '2' },
{ message: 'Значение должно быть десятичным числом с 2 знаками после запятой' }
)
price: string;
В типичных backend-приложениях (особенно NestJS)
@IsDecimal используется как слой:
Он не заменяет бизнес-валидацию, но обеспечивает строгий контракт входных данных.