Class-Validator 데코레이터(어노테이션) 설명
@IsOptional()
를 이용하면 undefined를 받을 수 있으면서 값이 존재할 때는 @IsString(), @IsNumber() 등으로 타입 체크도 가능하다.import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator';
export class RequestDto {
@ApiProperty({
description: 'name1 field'
})
@IsNotEmpty()
@IsString()
public name1: string;
@ApiProperty({
description: 'name2 field'
})
@IsNotEmpty()
@IsString()
public name2: string;
@ApiProperty({
description: 'name3 field'
})
@IsOptional()
@IsString()
public name3: string;
}
최근 새롭게 추가하고 있는 기능의 API에서 파라미터로 dto를 이용해 데이터를 받도록 만들고 있었다.
문제는 dto의 필드들 중 1개는 필수값이 아니었는데, 이 값이 없을 경우, undefined
로 받아 DynamoDB에 저장할 때 레코드의 속성이 생성되지 않도록 하고 싶었다.
따라서 아래의 코드처럼 DTO를 정의하고 class-validator
의 각종 데코레이터들을 이용해 유효성 체크 설정까지 했다.
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator';
export class RequestDto {
@ApiProperty({
description: 'name1 field'
})
@IsNotEmpty()
@IsString()
public name1: string;
@ApiProperty({
description: 'name2 field'
})
@IsNotEmpty()
@IsString()
public name2: string;
@ApiProperty({
description: 'name3 field'
})
@IsString()
public name3: string;
}
name3 필드의 경우 필수값이 아니기에 @IsNotEmpty()
데코레이터 설정을 하지 않고 숫자가 들어오면 안되므로 @IsString()
데코레이터으로 string 타입 체크만 하도록 설정했다.
그리고 아래와 같이 프론트 측에서 매개 변수를 생성하여 해당 API를 호출하였다.
const result: RequestDto = {
name1: 'NAME1 FIELD',
name2: 'NAME2 FIELD',
name3: undefined
};
결과는 400에러... 필수값이 아닌 name3의 유효성 체크가 되고 있었다.
{
"statusCode": 400,
"message": [
"name3 must be a string"
],
"error": "Bad Request"
}
dto 정의에서 name3 필드 정의를 잘못했나 하여 null 이나 undefined가 허용되도록 [?]를 붙여 다시 호출해 보았다.
@ApiProperty({
description: 'name3 field'
})
@IsString()
public name3?: string;
결과는 또 400에러... 계속해서 name3에 대한 체크가 실행된다.
{
"statusCode": 400,
"message": [
"name3 must be a string"
],
"error": "Bad Request"
}
필수값인 name2를 undefined로 하여 테스트 해 보았다.
const result: RequestDto = {
name1: 'NAME1 FIELD',
name2: undefined,
name3: 'NAME3 FIELD'
};
결과는 마찬가지로 400에러이지만 에러 메세지가 name3 때와 다르다.
name2는 @IsNotEmpty()
와 @IsString()
2개의 데코레이터로 유효성 체크를 하고 있는데 "name2 should not be empty"
란 에러 메세지가 추가적으로 나오는 것을 볼 수 있다.
{
"statusCode": 400,
"message": [
"name2 must be a string",
"name2 should not be empty",
],
"error": "Bad Request"
}
혹시나 하는 마음에 name2 필드에 빈 문자열(length === 0)을 넣어 테스트 해 보았다.
const result: RequestDto = {
name1: 'NAME1 FIELD',
name2: '',
name3: 'NAME3 FIELD'
};
결과를 통해서 에러 메세지가 1개만 나온 것을 확인할 수 있었다.
{
"statusCode": 400,
"message": [
"name2 should not be empty",
],
"error": "Bad Request"
}
@IsString()
: "name2 must be a string"@IsNotEmpty()
: "name2 should not be empty" 이 테스트를 통해 @IsString()
데코레이터는 string이 아닌 모든 값을 체크하고 있기 때문에 객체, 숫자를 포함해 null이나 undefined 값을 보낼 수가 없다는 것을 알게 되었다.
단순히 @IsNotEmpty()
데코레이터를 쓰지 않으면 간단하게 undefined를 API로 보낼 수 있다고 생각했는데 생각지도 못한 것에 막혔다. (필드 정의에서 [?]는 유효성 체크에서는 아무 의미가 없었다.)
이것 저것 찾아보다가 nest 공식 문서에서 찾은 아래의 내용을 이용해 다시 테스트 해보았다.
HINT
Instead of explicitly typing the @ApiProperty({ required: false }) you can use the @ApiPropertyOptional() short-hand decorator.
name3를 아래와 같이 변경하고, name3에 undefined를 넣어 API를 호출 해 보았다.
@ApiProperty({
description: 'name3 field',
required: false,
})
@IsString()
public name3: string;
결과는 변화없음...
{
"statusCode": 400,
"message": [
"name3 must be a string"
],
"error": "Bad Request"
}
현재 상태에서 이를 해결하기 위해서는 3가지 정도의 방법이 있다.
방법1. API를 호출 할 때, name3에 빈 문자열을 담아서 보낸다.
방법2. 방법1의 방법에서 DynamoDB에 저장할 때, name3가 빈 문자열인 경우 undefined로 변경하여 저장한다.
name3 ? undefined : name3
방법3. dto 정의에서 @IsString()
데코레이터를 사용하지 않는다.
모두 마음에 들지는 않지만 결국 아래와 같이 name3의 값이 undefined인 경우 빈 문자열을 넣어서 보내는 것으로 해결하기로 했다. (방법2)
const result: RequestDto = {
name1: 'NAME1 FIELD',
name2: 'NAME2 FIELD',
name3: ''
};
하지만 이건 아무리 생각해도 아닌 것 같고 오기가 생겨 좀더 찾아보기로 했는데, class-validator
의 공식 문서에는 뭔가 방법이 있지 않을까 해서 마지막으로 찾아보기로 했다.
역시 공식 사이트에는 전체 데코레이터들에 대한 소개가 있었고, 내가 원하는 방식과 가장 비슷한 설명이 있는 것은 한 번씩 테스트 해 보며 찾았다.
그 중 내 눈에 띄는 한 개의 데코레이터 소개.
@IsOptional()
Checks if given value is empty (=== null, === undefined) and if so, ignores all the validators on the property.
해당 필드의 값을 체크하여 null이나 undefined의 경우, 해당 필드의 다른 데코레이터들을 무시한다.
이 @IsOptional()
를 사용해보자!
dto의 name3 필드를 아래와 같이 변경했다.
@ApiProperty({
description: 'name3 field',
})
@IsOptional()
@IsString()
public name3: string;
그리고 API를 매개 변수의 name3에 undefined를 넣어 호출하였다.
const result: RequestDto = {
name1: 'NAME1 FIELD',
name2: 'NAME2 FIELD',
name3: undefined
};
결과는 OK!
{
"name1": "NAME1 FIELD",
"name2": "NAME2 FIELD"
}
내친김에 문자열(string) 체크도 해 본다. name3에 숫자를 넣어 보내본다. 에러가 나야한다.
const result: RequestDto = {
name1: 'NAME1 FIELD',
name2: 'NAME2 FIELD',
name3: 12345
};
결과는 400에러!
{
"statusCode": 400,
"message": [
"name3 must be a string"
],
"error": "Bad Request"
}
값이 존재할 경우에는 @IsString()
데코레이터에 의한 문자열 체크가 실행되고,
값이 존재하지 않을 경우에는 유효성 검사를 하는 다른 데코레이터들의 기능이 작동하지 않는다.
내가 찾던 데코레이션이었다.
혹시나 해서 @IsNotEmpty()
데코레이터도 함께 사용해보았다.
@ApiProperty({
description: 'name3 field',
})
@IsOptional()
@IsNotEmpty()
@IsString()
public name3: string;
이 경우에도 정상적으로 작동했다. 값이 존재하지 않을 경우에는 다른 데코레이터들이 작동하지 않았고, 값이 존재할 경우에는 문자열 타입 체크와 함께 빈 문자열 체크가 추가되었다.