82

I'm trying to validate nested objects using class-validator and NestJS. I've already tried following this thread by using the @Type decorator from class-transform and didn't have any luck. This what I have:

DTO:

class PositionDto {
  @IsNumber()
  cost: number;

  @IsNumber()
  quantity: number;
}

export class FreeAgentsCreateEventDto {

  @IsNumber()
  eventId: number;

  @IsEnum(FinderGamesSkillLevel)
  skillLevel: FinderGamesSkillLevel;

  @ValidateNested({ each: true })
  @Type(() => PositionDto)
  positions: PositionDto[];

}

I'm also using built-in nestjs validation pipe, this is my bootstrap:

async function bootstrap() {
  const app = await NestFactory.create(ServerModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(config.PORT);
}
bootstrap();

It's working fine for other properties, the array of objects is the only one not working.

4
  • 1
    I've just put your code in an empty sample project and it seems to work for me. What specific value is "not working"? What are your expectations? If you for example put "positions": [{"other": true}] in your body it rejects with 400. positions: [] is a valid value though. Commented Dec 14, 2018 at 20:35
  • I'm expecting that if you try positions: [1], it throws an error Commented Dec 14, 2018 at 20:37
  • @ArrayNotEmpty()? Commented Apr 15, 2021 at 8:49
  • 2023 and people still using this library... Thanks for question and answers! Commented Sep 26, 2023 at 13:26

4 Answers 4

104

for me, I would able to validate nested object with 'class-transformer'

import { Type } from 'class-transformer';

full example:

import {
  MinLength,
  MaxLength,
  IsNotEmpty,
  ValidateNested,
  IsDefined,
  IsNotEmptyObject,
  IsObject,
  IsString,
} from 'class-validator';
import { Type } from 'class-transformer';

class MultiLanguageDTO {
  @IsString()
  @IsNotEmpty()
  @MinLength(4)
  @MaxLength(40)
  en: string;

  @IsString()
  @IsNotEmpty()
  @MinLength(4)
  @MaxLength(40)
  ar: string;
}

export class VideoDTO {
  @IsDefined()
  @IsNotEmptyObject()
  @IsObject()
  @ValidateNested()
  @Type(() => MultiLanguageDTO)
  name!: MultiLanguageDTO;
}
Sign up to request clarification or add additional context in comments.

2 Comments

Use class-transformer-validator to validate it easily.
Why have you used the "!" on VideoDTO.name here?
44

You are expecting positions: [1] to throw a 400 but instead it is accepted.

According to this Github issue, this seems to be a bug in class-validator. If you pass in a primitive type (boolean, string, number,...) or an array instead of an object, it will accept the input as valid although it shouldn't.


I don't see any standard workaround besides creating a custom validation decorator:

import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator';

export function IsNonPrimitiveArray(validationOptions?: ValidationOptions) {
  return (object: any, propertyName: string) => {
    registerDecorator({
      name: 'IsNonPrimitiveArray',
      target: object.constructor,
      propertyName,
      constraints: [],
      options: validationOptions,
      validator: {
        validate(value: any, args: ValidationArguments) {
          return Array.isArray(value) && value.reduce((a, b) => a && typeof b === 'object' && !Array.isArray(b), true);
        },
      },
    });
  };
}

and then use it in your dto class:

@ValidateNested({ each: true })
@IsNonPrimitiveArray()
@Type(() => PositionDto)
positions: PositionDto[];

2 Comments

Is this supposed to transform each element of positions to PositionDto with its internal elements? If not, how can it be achieved, or do I necessarily have to do the transformations manually?
@AdrianGonzález Check this answer for nested validation/transformation: stackoverflow.com/a/53685045/4694994
3

I faced the same issue, so I created my own ValidateNested decorator.

import {
  ValidationOptions,
  registerDecorator,
  ValidationArguments,
  validateSync,
} from 'class-validator';
import { plainToClass } from 'class-transformer';

/**
 * @decorator
 * @description A custom decorator to validate a validation-schema within a validation schema upload N levels
 * @param schema The validation Class
 */
export function ValidateNested(
  schema: new () => any,
  validationOptions?: ValidationOptions
) {
  return function (object: Object, propertyName: string) {
    registerDecorator({
      name: 'ValidateNested',
      target: object.constructor,
      propertyName: propertyName,
      constraints: [],
      options: validationOptions,
      validator: {
        validate(value: any, args: ValidationArguments) {
          args.value;
          if (Array.isArray(value)) {
            for (let i = 0; i < (<Array<any>>value).length; i++) {
              if (validateSync(plainToClass(schema, value[i])).length) {
                return false;
              }
            }
            return true;
          } else
            return validateSync(plainToClass(schema, value)).length
              ? false
              : true;
        },
        defaultMessage(args) {
          if (Array.isArray(args.value)) {
            for (let i = 0; i < (<Array<any>>args.value).length; i++) {
              return (
                `${args.property}::index${i} -> ` +
                validateSync(plainToClass(schema, args.value[i]))
                  .map((e) => e.constraints)
                  .reduce((acc, next) => acc.concat(Object.values(next)), [])
              ).toString();
            }
          } else
            return (
              `${args.property}: ` +
              validateSync(plainToClass(schema, args.value))
                .map((e) => e.constraints)
                .reduce((acc, next) => acc.concat(Object.values(next)), [])
            ).toString();
        },
      },
    });
  };
}

Then you can use it like -

class Schema2 {

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

  @IsNotEmpty()
  @IsString()
  prop2: string;
}


class Schema1 {
  @IsNotEmpty()
  @IsString()
  prop3: string;

  @ValidateNested(Schema2)
  nested_prop: Schema2;
}

Works for both non-primitive arrays and javascript objects.

2 Comments

I don't the @ValidateNested(Schema2) is still valid with the current version of class-validator v0.14.0
Thank you this worked. But i'm getting some typing issue Type '(args: ValidationArguments | undefined) => string | undefined' is not assignable to type '(validationArguments?: ValidationArguments | undefined) => string'. Type 'string | undefined' is not assignable to type 'string'. Type 'undefined' is not assignable to type 'string'. No overload matches this call. Overload 1 of 2, '(o: { [s: string]: string; } | ArrayLike<string>): string[]', gave the following error. Argument of type '{ [type: string]: string; } | undefined' is not assignable to
1

I had same problem but got it fixed by doing:

import {
 ValidateNested,
 IsDefined,
 IsNotEmptyObject,
} from 'class-validator';

import { Type } from 'class-transformer';
import { ApiProperty } from '@nestjs/swagger';

@IsDefined()
@IsNotEmptyObject()
@ValidateNested()
@ApiProperty()
@Type(() => AddressDto)
address: AddressDto;

Using this answer stackoverflow.com/a/53685045/4694994

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.