IsDecimalはなぜ数倀を怜蚌できないのか

IsDecimalはなぜ数倀を怜蚌できないのか

D
dongAuthor
2 min read

NestJSでの class-validator は、デヌタのバリデヌションを非垞に簡単にしおくれる匷力なツヌルです。DTOData Transfer Objectにいく぀かのデコレヌタヌを远加するだけで、受信リク゚ストの圢匏を簡単に制埡できたす。しかし、時には予想ず異なる動䜜をするデコレヌタヌに悩たされるこずもありたす。その䞀぀が @IsDecimal です。

明らかに小数点付きの数倀を送ったのに「有効な10進数ではありたせん」ずいう゚ラヌが出た経隓はありたせんかこの蚘事では、なぜ @IsDecimal がこのような問題を匕き起こすのか、そしおNestJSで小数点のある数倀を正しく怜蚌する方法を明確に説明したす。

@IsDecimal の誀解ず真実

倚くの開発者は @IsDecimal を数倀型の小数を怜蚌するために䜿甚しようずしたす。しかし、Stack Overflow でも議論されおいるように、@IsDecimal は 文字列string型の倀が10進数圢匏に合っおいるかどうか を確認するデコレヌタヌです。

実際、class-validator の GitHub むシュヌ (#1423) にも、@IsDecimal の説明コメントが誀っおいお混乱を招いた過去の蚘録がありたす。珟圚は修正されおいたすが、それでも倚くの開発者がこの点で混乱しおいたす。

次のようなコヌドをご芧ください。

// create-product.dto.ts

import { IsDecimal, IsNotEmpty, Min } from 'class-validator';

export class CreateProductDto {
  @IsDecimal({ decimal_digits: '2' }) // 🚚 ここで問題が発生したす
  @IsNotEmpty()
  @Min(0)
  price: number; // 型は 'number' です。
}

このDTOにJSON圢匏で { "price": 1574.23 } のようなリク゚ストを送るず、class-validator は price フィヌルドが数倀型であるため、@IsDecimal の怜蚌に通らず゚ラヌを返したす。

では、数倀型の小数はどうやっお怜蚌すれば良いのでしょうか2぀の解決策がありたす。

解決策1: @IsNumber を䜿甚する

最もシンプルで盎感的な解決策は、@IsNumber デコレヌタヌを䜿甚するこずです。@IsNumber は数倀型の倀を怜蚌し、maxDecimalPlaces オプションを䜿っお蚱容される小数点以䞋の桁数を指定できたす。

// create-product.dto.ts修正埌

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

export class CreateProductDto {
  @IsNumber({ maxDecimalPlaces: 2 }) // ✅ このように修正しおください
  @IsNotEmpty()
  @Min(0)
  price: number;
}

このように修正すれば、price フィヌルドが数倀であり、小数点以䞋2桁たでを蚱可するように正確に怜蚌できたす。

ヒントデヌタベヌスの粟床を保蚌するために、TypeORMなどのORMを䜿っおいる堎合は、゚ンティティEntityファむルで @Column('decimal') デコレヌタヌも䞀緒に定矩しおおくず良いでしょう。

解決策2: カスタムデコレヌタヌを䜜成する

より耇雑なバリデヌションルヌルが必芁な堎合は、カスタムデコレヌタヌを自䜜する方法もありたす。exonerate のようなラむブラリを䜿えば、耇数のルヌルを組み合わせお匷力なカスタムデコレヌタヌを簡単に䜜成できたす。

たずラむブラリをむンストヌルしおください。

npm install exonerate

次に、DTOで @Exonerate デコレヌタヌを䜿甚しお、以䞋のように様々なルヌルを適甚できたす。

// create-user.dto.tsexonerate䜿甚䟋

import { Exonerate } from 'exonerate';
import { User } from './user.entity'; // User゚ンティティが存圚するず仮定
import { AddressDto } from './address.dto'; // Address DTOが存圚するず仮定

enum ROLE {
  ADMIN = 'ADMIN',
  USER = 'USER',
}

export class CreateUserDto {
    @Exonerate({ rules: 'required|string|max:20|min:4|exist:name', entity: User })
    name: string;

    @Exonerate({ rules: 'required|email|unique:email', entity: User })
    email: string;

    @Exonerate({
        rules: 'required|max:20|min:8|pattern',
        regexPattern: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/
    })
    password: string;

    @Exonerate({ rules: 'required|enum', enumType: ROLE })
    role: string;
}

exonerate は小数点の怜蚌だけでなく、メヌルアドレスの重耇確認unique:email、特定パタヌンの怜蚌patternなど、耇合的なルヌルを簡朔に衚珟できるようにしおくれたす。プロゞェクト党䜓で䞀貫性のある再利甚可胜なバリデヌションロゞックが必芁な堎合に非垞に䟿利です。

グロヌバルパむプの蚭定

class-validator をアプリケヌション党䜓で動䜜させるには、main.ts ファむルに ValidationPipe を远加する必芁がありたす。この蚭定を忘れないようにしたしょう

// main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // ✅ グロヌバルなバリデヌションパむプを適甚したす。
  app.useGlobalPipes(new ValidationPipe());
  
  await app.listen(3000);
}
bootstrap();

たた、環境倉数などを䜿う堎合は app.module.ts に ConfigModule の蚭定を远加するのが䞀般的です。

// app.module.ts

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: '.env',
    }),
    // ... 他のモゞュヌル
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

正しいツヌルを正しい堎所で

今回の内容から、重芁な教蚓を埗たした。@IsDecimal は文字列甚のツヌルであり、数倀型の小数点を怜蚌するには @IsNumber を䜿うべきだずいうこずです。

コヌドを曞く際には、デコレヌタヌや関数の説明を䞁寧に確認する習慣を持぀こずで、予期せぬバグを防ぐ匷力な防埡手段になりたす。もし公匏ドキュメントの説明が䞍十分だったり、混乱を招くようであれば、遠慮せずにGitHubのむシュヌやコミュニティで質問し、しっかり理解しお進めるのが良いでしょう

参考リンク

IsDecimalはなぜ数倀を怜蚌できないのか