typestack / class-validator

Decorator-based property validation for classes.
MIT License
10.68k stars 775 forks source link

feat: support multi-dimensional arrays #538

Open Eldar-X opened 4 years ago

Eldar-X commented 4 years ago

Hello, I am trying to validate the arrays in arrays but I could not find a correct method.

The structure looks like this:

class MyInnerDto {
 @IsString()
  name: string;
}

class TestDto {
@IsArray({ each: true })
 myArray:MyInnerDto[][];
}
jBouyoud commented 4 years ago

Hello,

I've the same kind of issue with Map and Array values. The test case below show validation error on nestedMap but not on nestedWithArray. Is there another tips to validate this kind of structure ? or it's a bug ?

With class-validator": "0.11.0"

import { IsString, ValidateNested, Validator } from 'class-validator';

describe('NestedValidation', () => {
  class NestedClass {
    @IsString()
    name: string;

    constructor(name: any) {
      this.name = name;
    }
  }

  class MasterClass {
    @ValidateNested({ each: true })
    nestedMap: Map<string, NestedClass>;

    @ValidateNested({ each: true })
    nestedWithArray: Map<string, NestedClass[]>;

    constructor(
      nestedMap: Map<string, NestedClass>,
      nestedWithArray: Map<string, NestedClass[]>,
    ) {
      this.nestedMap = nestedMap;
      this.nestedWithArray = nestedWithArray;
    }
  }

  describe('validation', () => {
    let validator: Validator;

    beforeAll(() => {
      validator = new Validator();
    });

    it('should validate nested object array', async () => {
      const nestedMap = new Map<string, NestedClass>();
      nestedMap.set('test', new NestedClass(42));
      const nestedMapWithArray = new Map<string, NestedClass[]>();
      nestedMapWithArray.set('test', [new NestedClass(42)]);
      const masterClass = new MasterClass(nestedMap, nestedMapWithArray);

      const errors = await validator.validate(masterClass);

      function expectNestedError(property: string) {
        const nestedError = errors.find(error => error.property === property);
        expect(nestedError).toBeDefined();
        expect(nestedError).toMatchObject({ property, target: masterClass, });
      }
      expectNestedError('nestedMap');
      expectNestedError('nestedWithArray');
    });
  });
});

Thanks

vlapo commented 4 years ago

https://github.com/typestack/class-validator/pull/539 fixed multi-dimensional arrays for @ValidatedNested. But we should also support multi-dimensional arrays for cases like:

@IsNumber({}, {
    each: true
})
tags: number[][];

Note: I see one edge case here. What if I want to validate only first, second, third, ..., ... level of multi-dimensional array? E.g.:

@IsArray({}, {
    each: true
})
@IsNumber({ each: true })
tags: number[][][][][];
Eldar-X commented 4 years ago

Maybe we should add an option maxDepth for this?

Timtam commented 3 years ago

whats the status on the situation here? I'm facing a simple 2d numeric array validation issue here and this issue is on hold for over a year now because of a rather rare edge case whilst simple multi-dimensional array checks are already possible but not yet merged. Any way to get into this again?

UNDERCOVERj commented 2 years ago

mark

mainakchhari commented 2 years ago

Any word on this? Can anyone suggest a workaround using custom validators that can do the needful? I need to validate multi dimensional arrays of various types, ie. enums, strings, numbers, objectids (mongodb)

jjh-reciprocity commented 1 year ago

Simple custom decorator example below.

This validates that the property:

  1. is an array
  2. each element is also an array
  3. each sub array has 3 entries
  4. each sub entry is a string

You can tweak this for your needs

import { registerDecorator } from 'class-validator';

export function PermissionsValidator() {
  // eslint-disable-next-line @typescript-eslint/ban-types
  return function (object: Object, propertyName: string) {
    registerDecorator({
      name: 'permissions',
      target: object.constructor,
      propertyName: propertyName,
      validator: {
        validate(value: unknown) {
          return (
            Array.isArray(value) &&
            value.length > 0 &&
            value.every((row) => {
              return (
                Array.isArray(row) &&
                row.length === 3 &&
                row.every((cell) => typeof cell === 'string')
              );
            })
          );
        },
      },
    });
  };
}