class-validator icon indicating copy to clipboard operation
class-validator copied to clipboard

question: accept only objects of a given validate-required form within an array

Open AdaptingAFM opened this issue 2 years ago • 9 comments

I was trying to... Allow only an array of objects to be passed on a given property

The problem: ValidateNested allows both objects and arrays, making it possible to pass any amounts of nested arrays as long as in any moment where there's any other type defined within the matrixes it is according to the other constrains.

@ArrayMinSize(0)
@ArrayMaxSize(5)
@ValidateNested({ each: true })
@Type(() => OfferPricingDTO)
pricing: OfferPricingDTO[];
// allows [[[]]], where my interest is to only allow objects with the passed type as direct items of this property.

AdaptingAFM avatar Mar 24 '22 19:03 AdaptingAFM

I am also struggling with this issue, please look into it.

gitSambhal avatar Aug 03 '22 12:08 gitSambhal

Just came across this issue. Also noticed that while validating objects (not within an array) it accepts an empty array successfully.

zxar7 avatar Aug 30 '22 07:08 zxar7

Related stackoverflow: https://stackoverflow.com/questions/73527819/nestjs-validate-an-array-of-objects

Same issue here...

LarsFlieger avatar Aug 30 '22 08:08 LarsFlieger

If I understand this correctly, you could end up with a lot of different objects (or empty arrays) in your array, and you want to make sure that they are validated as an OfferPricingDTO?

Can you show us the definition and validation decorators for the OfferPricingDTO class?

I think a possible solution here is to use an abstract Validation class such as this:

import { plainToInstance } from 'class-transformer';
import { ValidationError, validate, validateOrReject } from 'class-validator';

export type StaticThis<T> = { new (): T };

export abstract class Validation {
  static async check<T extends Validation>(
    this: StaticThis<T>,
    data: T,
    validate = true,
  ): Promise<T> {
    const that = plainToInstance(this, data);
    if (validate) {
      await validateOrReject(that, {
        skipMissingProperties: true,
      });
    }

    return that;
  }

  static validate<T extends Validation>(
    this: StaticThis<T>,
    data: T,
    reject = true,
  ): Promise<void | ValidationError[]> {
    const that = plainToInstance(this, data);
    if (reject) {
      return validateOrReject(that, {
        skipMissingProperties: true,
      });
    }

    return validate(that);
  }
}

You can then extend this class in your DTO, which lets you use OfferPricingDTO.validate(someObject) or OfferPricingDTO.check(someObject) to validate an OfferPricingDTO:

export class OfferPricingDTO extends Validation {
  @IsString()
  myProp: string
}

This lets you validate your array of OfferPricingDTO using @Validate like so:

export class MyClass {
  @ArrayMinSize(0)
  @ArrayMaxSize(5)
  @Validate(OfferPricingDTO.validate, { each: true })
  @Type(() => OfferPricingDTO)
  pricing: OfferPricingDTO[];
}

I'm sure there is a way to achieve this without using the Validation class I have described here, but I use that in all my class-validator projects since I find it very convenient. It all depends on how you construct your objects and such, and if you use class-transformer or not.

Anyways, the @Validate decorator is your friend here.

braaar avatar Aug 30 '22 08:08 braaar

I still think there is a legitimate issue here. It doesn't seem intuitive or practical that ValidateNested accepts empty arrays.

braaar avatar Aug 30 '22 08:08 braaar

I still think there is a legitimate issue here. It doesn't seem intuitive or practical that ValidateNested accepts empty arrays.

I totally agree on this. A custom validation is a hotfix for this issue however the current implementation / interface is misunderstanding for developers. And it can get abused in production versions because you can pass the validation layer.

LarsFlieger avatar Aug 30 '22 08:08 LarsFlieger

I think the issue is twofold:

  1. It's not nice that empty arrays are accepted by ValidateNested. There should at least be an option to disable that behaviour or something.
  2. It would be nice to have a simple way to tell class-validator to validate an object property of a specific class, similar to @IsEnum(entity: object). For example, it could look like this: @ValidateNested(OfferPricingDTO, {each: true})

braaar avatar Aug 30 '22 09:08 braaar

I hope that @NoNameProvided will look at it, because that should be fixed.

giacomo avatar Sep 28 '22 10:09 giacomo

Is it already possible to do this easily ? Default behavior still allows empty Arrays and arrays of arrays.

Lukas-Sturm avatar Sep 18 '23 10:09 Lukas-Sturm