You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nestjs+Class-validator校验Decimal值遇异常,求最佳实践

NestJS 处理 Decimal 数值校验的最佳实践

问题根源

你遇到的问题核心在于 @IsDecimal 装饰器的设计逻辑:它默认只校验字符串类型,而你把 price 定义成了 number 类型。JSON 传输的数字本质是浮点数,JS 里的浮点数会有精度丢失问题(比如 1574.23 实际存储可能是近似值),这也会导致 @IsDecimal 对 number 类型的校验失效。改用 @IsNumberString 无效是因为你传的是数字而非字符串格式的数字。

解决方案与最佳实践

方案1:用字符串传输 Decimal(推荐用于金额等精确场景)

把 DTO 中的 price 改为字符串类型,直接用 @IsDecimal 校验,之后在服务层转成需要的数值类型:

// create-product.dto.ts
import { IsDecimal, IsNotEmpty, Min } from 'class-validator';

export class CreateProductDto {
  @IsDecimal({ force_decimal: true, decimal_digits: '2' })
  @IsNotEmpty({ message: 'Price is required' })
  price: string;

  // 其他字段...
}

在服务层处理转换(如果需要转成 number 或精确 Decimal 实例):

// product.service.ts
import { Injectable } from '@nestjs/common';
import { CreateProductDto } from './dto/create-product.dto';
import Decimal from 'decimal.js';

@Injectable()
export class ProductService {
  create(createProductDto: CreateProductDto) {
    // 转成精确 Decimal 实例(推荐金额场景)
    const price = new Decimal(createProductDto.price);
    // 或转成 number(注意精度风险)
    // const price = Number(createProductDto.price);
    
    // 后续业务逻辑...
    return { ...createProductDto, price };
  }
}

方案2:自定义校验器处理 number 类型

如果必须用 number 类型接收,需要自定义校验器来检查小数位数,同时规避浮点数精度问题:

  1. 先创建自定义装饰器:
// is-decimal-number.validator.ts
import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator';

export function IsDecimalNumber(decimalDigits: string, validationOptions?: ValidationOptions) {
  return function (object: Object, propertyName: string) {
    registerDecorator({
      name: 'isDecimalNumber',
      target: object.constructor,
      propertyName: propertyName,
      options: validationOptions,
      validator: {
        validate(value: number, args: ValidationArguments) {
          if (typeof value !== 'number') return false;
          // 把数字转成字符串,处理浮点数精度问题
          const numStr = value.toFixed(Number(decimalDigits));
          // 检查小数位数是否符合要求
          const regex = new RegExp(`^\\d+\\.\\d{${decimalDigits}}$`);
          return regex.test(numStr);
        },
        defaultMessage(args: ValidationArguments) {
          return `$property must be a valid decimal number with ${decimalDigits} decimal places`;
        },
      },
    });
  };
}
  1. 在 DTO 中使用:
// create-product.dto.ts
import { IsNotEmpty, Min } from 'class-validator';
import { IsDecimalNumber } from './is-decimal-number.validator';

export class CreateProductDto {
  @IsDecimalNumber('2', { message: 'Price must be a valid decimal number with 2 decimal places' })
  @IsNotEmpty({ message: 'Price is required' })
  @Min(0, { message: 'Price must be greater than or equal to 0' })
  price: number;

  // 其他字段...
}

方案3:结合 Transform 与 decimal.js 处理精确数值

如果需要直接在 DTO 中得到精确的 Decimal 实例,可以用 class-transformer 的 @Transform 装饰器:

// create-product.dto.ts
import { IsNotEmpty, Min } from 'class-validator';
import { Transform } from 'class-transformer';
import Decimal from 'decimal.js';

export class CreateProductDto {
  @Transform(({ value }) => new Decimal(value))
  @IsNotEmpty({ message: 'Price is required' })
  // 校验数值不小于0
  @Min(0, { 
    message: 'Price must be greater than or equal to 0',
    validator: {
      validate(value: Decimal) {
        return value.greaterThanOrEqualTo(0);
      }
    }
  })
  // 校验小数位数为2位
  @Min(0, {
    message: 'Price must have exactly 2 decimal places',
    validator: {
      validate(value: Decimal) {
        return value.decimalPlaces() === 2;
      }
    }
  })
  price: Decimal;

  // 其他字段...
}

总结

  • 涉及金额、税率等需要精确计算的场景,优先用字符串传输 Decimal,再转成 decimal.js 实例处理,避免浮点数精度丢失。
  • 如果必须用 number 类型,一定要自定义校验器,不要依赖原生的 @IsDecimal。

内容的提问来源于stack exchange,提问作者berk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 12:25:11