92

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)

1

6 Answers 6

207

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.

Sign up to request clarification or add additional context in comments.

6 Comments

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.
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?
To answer @Zephyr question, use @IsObject({ each: true }). There is a short example here with the different errors you can get.
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
I worked ... but How on earth did I needed to use class-transformer to perform a nested validation with class-validator? 😮
|
10

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 😃

Comments

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.

1 Comment

This is nicely resolve number of elements question - thank you. Validation of objects inside array still valid
3

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

Comments

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)

Comments

2

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;
      }
   }
}

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.