I´m trying to validate the body of a route on NestJS and i´m using the class-validator package, my route is receiving the data as multipat because this route also accepts a file upload. Here is the main DTO:
import { Transform, Type } from "class-transformer";
import { IsArray, IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsPositive, IsString, ValidateNested } from "class-validator";
import { CreateProductOptionalGroupDto } from ".";
export class CreateProductDto {
@IsString()
@IsOptional()
description?: string;
@IsString()
@IsNotEmpty()
short_description: string;
@Transform(({ value }) => parseFloat(value))
@IsNumber()
@IsPositive()
@IsNotEmpty()
price: number;
@Transform(({ value }) => {
if (value === 'true' || value === true) return true
if (value === 'false' || value === false) return false
return undefined
})
@IsBoolean()
@IsOptional()
active?: boolean
@Transform(({ value }) => { console.log(JSON.parse(value)); return JSON.parse(value) })
@ValidateNested({ each: true })
@IsArray()
@Type(() => CreateProductOptionalGroupDto)
optionals_group: CreateProductOptionalGroupDto[];
}
This DTO includes an array containing group of optionals, as right now i´m keeping only the description on it, here is my CreateProductOptionalGroupDto
import { IsNotEmpty, IsString } from "class-validator";
export class CreateProductOptionalGroupDto {
@IsString()
@IsNotEmpty()
description: string
}
When i send the request class-validator whitelists the description property of the object inside optionals_group array i get the following as the output:
{
"description": "Phone",
"short_description": "A very nice phone",
"price": 623.05,
"optionals_group": [
{}
]
}
if i add the forbidNonWhitelisted property to the validation pipe i get
{
"message": [
"optionals_group.0.property description should not exist"
],
"error": "Bad Request",
"statusCode": 400
}
if i remove both whitelist and forbidNonWhitelisted i get all the properties without any validation.
For me it looks like @Type(() => CreateProductOptionalGroupDto)
is not doing it´s job, what i´m doing wrong ?
The issue arises due to how the optionals_group
is being parsed and transformed. You need to make sure that when parsing and transforming the array of objects (optionals_group
), the resulting values are properly converted to instances of the DTO (CreateProductOptionalGroupDto
)
Fix:
@Transform(({ value }) => {
const parsed = JSON.parse(value);
value = parsed.map(obj => plainToClass(CreateProductOptionalGroupDto, obj));
console.log("value", value);
return value;
})
@ValidateNested({ each: true })
@IsArray()
@Type(() => CreateProductOptionalGroupDto)
optionals_group: CreateProductOptionalGroupDto[];
The @Transform
decorator is used to handle the incoming raw value, which in your case is a JSON string. After parsing it, we map each object in the parsed array to an instance of CreateProductOptionalGroupDto
using plainToClass
.
The plainToClass
function is important because it ensures that the validation decorators (like @IsString()
and @IsNotEmpty()
) are applied to each object inside the array
Behavior 1 (without passing description in optionals_group
):
{
"optionals_group": [{}]
}
Will result in:
{
"message": [
"optionals_group.0.description should not be empty",
"optionals_group.0.description must be a string"
],
"error": "Bad Request",
"statusCode": 400
}
Behavior 2 (passing description in optionals_group
)
[
{ "description": "Testing description" },
{ "description": "Testing description" }
]
Results in:
{
"short_description": "Test",
"price": 100,
"optionals_group": [
{
"description": "Testing description"
},
{
"description": "Testing description"
}
]
}