Search code examples
javascriptnode.jstypescriptnestjsclass-validator

Validate nested objects using class validator and nestjs


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.


Solution

  • 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[];