68

I am using class-validator package with NestJS and I am looking to validate an array of objects that need to have exactly 2 objects with the same layout:

So far I have:

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

export class AuthParam {
  @IsNumber()
  id: number;

  @IsString()
  type: string;

  @IsString()
  value: string;
}

and

import { IsArray, ValidateNested } from 'class-validator';
import { AuthParam } from './authParam.model';

export class SignIn {
  @IsArray()
  @ValidateNested({ each: true })
  authParameters: AuthParam[];
}

per @kamilg response (I am able to enforce exacly 2 elements):

import { IsArray, ValidateNested, ArrayMinSize, ArrayMaxSize } from 'class-validator';
import { AuthParam } from './authParam.model';

export class SignInModel {
  @IsArray()
  @ValidateNested({ each: true })
  @ArrayMinSize(2)
  @ArrayMaxSize(2)
  authParameters: AuthParam[];
}

I still can pass an empty array or an array with some other objects not related to AuthParam.

How I should modify it get validation?

Also how I can enforce mandatory 2 elements in the array? MinLength(2) seems to be regarding string... (resolved)

bensiu
  • 24,660
  • 56
  • 77
  • 117
  • https://github.com/typestack/class-validator/pull/295 Was just published in `v0.10.2`, so it should help, hopefully! – kamilg Oct 14 '19 at 12:58

6 Answers6

165

Add @Type(() => AuthParam) to your array and it should be working. Type decorator is required for nested objects(arrays). Your code becomes

import { IsArray, ValidateNested, ArrayMinSize, ArrayMaxSize } from 'class-validator';
import { AuthParam } from './authParam.model';
import { Type } from 'class-transformer';

export class SignInModel {
  @IsArray()
  @ValidateNested({ each: true })
  @ArrayMinSize(2)
  @ArrayMaxSize(2)
  @Type(() => AuthParam)
  authParameters: AuthParam[];
}

Be careful if you are using any exception filter to modify the error reponse. Make sure you understand the structure of the class-validator errors.

Yousef khan
  • 2,764
  • 3
  • 14
  • 16
  • Hi, any idea on how to achieve this for an array of numbers in a query param? imagine im trying to validate [www.url.com/path?ids=1,2,3] , where [ids] should be an array of numbers, and nothing else. tried converting your answer, but with no success so far. – Maor Barazani Nov 07 '21 at 22:11
  • 2
    Hi, I tried something similar and is working as expected. But I noticed that when the array contains an empty array (as opposed to say the AuthParam obj) it does not throw an error. How can I ensure that the array only contains objects and not arrays? – Zephyr Aug 29 '22 at 10:41
  • Trying something similar but it doesn't seem to be working: https://stackoverflow.com/questions/73654324/class-validator-validate-request-body-as-array – myselfmiqdad Sep 08 '22 at 22:05
  • 1
    To answer @Zephyr question, use `@IsObject({ each: true })`. There is a [short example here with the different errors you can get](https://www.gistshare.com/notes/1352/class-validator-array-objects-validation-example). – faboulaws Jan 16 '23 at 20:50
  • In case someone runs into the same issue I had, if you're writing unit tests, you might need to `import "reflect-metadata"` as the first line of your test, otherwise the `@Type` decorator is going to cause a `Reflect.getMetadata() is not a function` error – Sebastian Mar 16 '23 at 17:08
  • I worked ... but How on earth did I needed to use `class-transformer` to perform a nested validation with `class-validator`? – Hugo Bayoud May 11 '23 at 09:56
8

I Know I Am Late But Facing Some Issue With Type, Then Try Another Way To Implement This:

export class AuthParam {
    @IsNumber()
    id: number;
  
    @IsString()
    type: string;
  
    @IsString()
    value: string;
  }

Validation function

@ValidatorConstraint()
export class IsAuthArray implements ValidatorConstraintInterface {
    public async validate(authData: AuthParam[], args: ValidationArguments) {
        return Array.isArray(authData) && authData.reduce((a, b) => a && (typeof b.id === "number") && typeof b.type === "string" && typeof b.field === "string", true);
    }
}

export class SignInModel {
    @IsNotEmpty()
    @IsArray()
    @ArrayMinSize(2)
    @ArrayMaxSize(2)
    @Validate(IsAuthArray, {
        message: "Enter valid value .",
    })
    authParameters: AuthParam[];
  }

Maybe It Will Help Someone

Dhaval
  • 868
  • 12
  • 22
3

You can use the following:

validator.arrayNotEmpty(array); // Checks if given array is not empty.

validator.arrayMinSize(array, min); // Checks if array's length is at least `min` number.

(https://github.com/typestack/class-validator#manual-validation)

You may want to consider writing custom validator which would better reflect the business requirement you have.

kamilg
  • 693
  • 4
  • 10
  • This is nicely resolve number of elements question - thank you. Validation of objects inside array still valid – bensiu Oct 11 '19 at 17:40
2

const param1: AuthParam = Object.assign(new AuthParam(), {
  id: 1,
  type: 'grant',
  value: 'password'
})

const param2: AuthParam = Object.assign(new AuthParam(), {
  id: 1,
  type: 4,
  value: 'password'
})

const signInTest = new SignInModel()
signInTest.authParameters = [param1, param2]

validate(signInTest).then(e => {
  console.log(e[0].children[0].children[0])
})

This works correctly, this is:

ValidationError {
  target: AuthParam { id: 1, type: 4, value: 'password' },
  value: 4,
  property: 'type',
  children: [],
  constraints: { isString: 'type must be a string' } }

so I may only assume that object which is being validated, is not an instance of AuthParam

const param2: AuthParam = {
  id: 1,
  type: 4,
  value: 'password'
} as any

as expected, there aren't any decorators on this object (which may be true for Nest.js controllers and nested objects from body/req) - so validation is ignored.

Please check this (tl;dr - @Type decorator form class-transformer)

kamilg
  • 693
  • 4
  • 10
1

I was able to validate array through this snippet

import { IsArray, ValidateNested} from 'class-validator';

import { Type } from 'class-transformer';




  @IsArray()
  @ValidateNested({ each: true })
  @Type(() => TypeOfEachObject)
  nameOfArray: TypeOfEachObject[];
Al Fahad
  • 2,378
  • 5
  • 28
  • 37
0

Without using @Type annotation, following approach also works great:

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

export class AuthParam {
  @IsNumber()
  id: number;

  @IsString()
  type: string;

  @IsString()
  value: string;

constructor(payload: AuthParam) {
    this.id = payload.id;
    this.type = payload.type;
    this.value = payload.value;
  }
}

and

import { IsArray, ValidateNested, ArrayMinSize, ArrayMaxSize } from 'class-validator';
import { AuthParam } from './authParam.model';

export class SignInModel {
  @IsArray()
  @ValidateNested({ each: true })
  @ArrayMinSize(2)
  @ArrayMaxSize(2)
  authParameters: AuthParam[];
  constructor(payload: SignInModel) {
      if (isArray(payload.authParameters)) {
         const authParameters = payload.authParameters.map(ap => new AuthParam(ap));
         this.authParameters = authParameters;
      } else {
         this.authParameters = payload.authParameters;
      }
   }
}
manojadams
  • 2,314
  • 3
  • 26
  • 30