Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

validate nested objects using class-validator in nest.js controller

I want to validate body payload using class-validator in a nest.js controller. My currency.dto.ts file is like this:

import {
  IsNotEmpty,
  IsString,
  ValidateNested,
  IsNumber,
  IsDefined,
} from 'class-validator';

class Data {

  @IsNotEmpty()
  @IsString()
  type: string;

  @IsNotEmpty()
  @IsNumber()
  id: number;
}

export class CurrencyDTO {
  @ValidateNested({ each: true })
  @IsDefined()
  data: Data[];
}

and in my nest.js controller, I use it like this.

  @Post()
  @UseGuards(new AuthTokenGuard())
  @UsePipes(new ValidationPipe())
  addNewCurrency(@Req() req, @Body() data: CurrencyDTO) {
    console.log('data', data);
  }

my validation pipe class is like this:

import {
  PipeTransform,
  Injectable,
  ArgumentMetadata,
  BadRequestException,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
import { validate, IsInstance } from 'class-validator';
import { plainToClass, Exclude } from 'class-transformer';

@Injectable()
export class ValidationPipe implements PipeTransform<any> {
  async transform(value: any, metadata: ArgumentMetadata) {
    if (value instanceof Object && this.isEmpty(value)) {
      throw new HttpException(
        `Validation failed: No Body provided`,
        HttpStatus.BAD_REQUEST,
      );
    }
    const { metatype } = metadata;
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    const object = plainToClass(metatype, value);
    const errorsList = await validate(object);
    if (errorsList.length > 0) {
      const errors = [];
      for (const error of errorsList) {
        const errorsObject = error.constraints;
        const { isNotEmpty } = errorsObject;
        if (isNotEmpty) {
          const parameter = isNotEmpty.split(' ')[0];
          errors.push({
            title: `The ${parameter} parameter is required.`,
            parameter: `${parameter}`,
          });
        }
      }
      if (errors.length > 0) {
        throw new HttpException({ errors }, HttpStatus.BAD_REQUEST);
      }
    }
    return value;
  }

  private toValidate(metatype): boolean {
    const types = [String, Boolean, Number, Array, Object];
    return !types.find(type => metatype === type);
  }
  private isEmpty(value: any) {
    if (Object.keys(value).length > 0) {
      return false;
    }
    return true;
  }
}

This validation pipe works fine for all except for nested objects. Any idea what am I doing wrong here? My body payload is like this:

{
"data": [{
    "id": 1,
    "type": "a"
}]
}
like image 419
Usama Tahir Avatar asked Dec 06 '18 11:12

Usama Tahir


2 Answers

Try specifying the nested type with @Type:

import { Type } from 'class-transformer';

export class CurrencyDTO {
  @ValidateNested({ each: true })
  @Type(() => Data)
  data: Data[];
}

For a nested type to be validated, it needs to be an instance of a class not just a plain data object. With the @Type decorator you tell class-transformer to instantiate a class for the given property when plainToClass is called in your VaildationPipe.

If you are using the built-in ValidationPipe make sure you have set the option transform: true.

like image 169
Kim Kern Avatar answered Oct 05 '22 09:10

Kim Kern


At least in my case, the accepted answer needed some more info. As is, the validation will not run if the key data does not exist on the request. To get full validation try:

@IsDefined()
@IsNotEmptyObject()
@ValidateNested()
@Type(() => CreateOrganizationDto)
@ApiProperty()
organization: CreateOrganizationDto;
like image 3
Xen_mar Avatar answered Oct 05 '22 11:10

Xen_mar