Skip to content

Commit 62678e1

Browse files
authoredMar 28, 2020
fix: ValidateNested support multi-dimensional arrays (#539)
1 parent 6457326 commit 62678e1

File tree

3 files changed

+71
-22
lines changed

3 files changed

+71
-22
lines changed
 

‎README.md

+13
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,19 @@ export class Post {
318318
}
319319
```
320320

321+
It also works with multi-dimensional array, like :
322+
323+
```typescript
324+
import {ValidateNested} from "class-validator";
325+
326+
export class Plan2D {
327+
328+
@ValidateNested()
329+
matrix: Point[][];
330+
331+
}
332+
```
333+
321334
## Validating promises
322335

323336
If your object contains property with `Promise`-returned value that should be validated, then you need to use the `@ValidatePromise()` decorator:

‎src/validation/ValidationExecutor.ts

+5-20
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export class ValidationExecutor {
183183

184184
this.defaultValidations(object, value, metadatas, validationError.constraints);
185185
this.customValidations(object, value, customValidationMetadatas, validationError);
186-
this.nestedValidations(value, nestedValidationMetadatas, validationError.children);
186+
this.nestedValidations(value, nestedValidationMetadatas, validationError.children, definedMetadatas, metadatas);
187187

188188
this.mapContexts(object, value, metadatas, validationError);
189189
this.mapContexts(object, value, customValidationMetadatas, validationError);
@@ -327,18 +327,8 @@ export class ValidationExecutor {
327327
});
328328
}
329329

330-
private nestedPromiseValidations(value: any, metadatas: ValidationMetadata[], errors: ValidationError[]) {
331-
332-
if (!(value instanceof Promise)) {
333-
return;
334-
}
335-
336-
this.awaitingPromises.push(
337-
value.then(resolvedValue => this.nestedValidations(resolvedValue, metadatas, errors))
338-
);
339-
}
340-
341-
private nestedValidations(value: any, metadatas: ValidationMetadata[], errors: ValidationError[]) {
330+
private nestedValidations(value: any, metadatas: ValidationMetadata[], errors: ValidationError[],
331+
definedMetadatas: ValidationMetadata[], allMetadatas: ValidationMetadata[]) {
342332

343333
if (value === void 0) {
344334
return;
@@ -352,19 +342,14 @@ export class ValidationExecutor {
352342
return;
353343
}
354344

355-
const targetSchema = typeof metadata.target === "string" ? metadata.target as string : undefined;
356-
357345
if (value instanceof Array || value instanceof Set || value instanceof Map) {
358346
// Treats Set as an array - as index of Set value is value itself and it is common case to have Object as value
359347
const arrayLikeValue = value instanceof Set ? Array.from(value) : value;
360348
arrayLikeValue.forEach((subValue: any, index: any) => {
361-
const validationError = this.generateValidationError(value, subValue, index.toString());
362-
errors.push(validationError);
363-
364-
this.execute(subValue, targetSchema, validationError.children);
349+
this.performValidations(value, subValue, index.toString(), definedMetadatas, allMetadatas, errors);
365350
});
366-
367351
} else if (value instanceof Object) {
352+
const targetSchema = typeof metadata.target === "string" ? metadata.target as string : metadata.target.name;
368353
this.execute(value, targetSchema, errors);
369354

370355
} else {

‎test/functional/nested-validation.spec.ts

+53-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import "es6-shim";
22
import {Contains, IsDefined, MinLength, ValidateNested} from "../../src/decorator/decorators";
33
import {Validator} from "../../src/validation/Validator";
44
import {expect} from "chai";
5-
import {inspect} from "util";
65
import {ValidationTypes} from "../../src/validation/ValidationTypes";
76

87
import {should, use } from "chai";
@@ -67,6 +66,12 @@ describe("nested validation", function () {
6766

6867
@ValidateNested()
6968
mySubClasses: MySubClass[];
69+
70+
@ValidateNested()
71+
mySubSubClasses: MySubClass[][];
72+
73+
@ValidateNested()
74+
mySubSubSubClasses: MySubClass[][][];
7075
}
7176

7277
const model = new MyClass();
@@ -76,8 +81,13 @@ describe("nested validation", function () {
7681
model.mySubClasses = [new MySubClass(), new MySubClass()];
7782
model.mySubClasses[0].name = "my";
7883
model.mySubClasses[1].name = "not-short";
84+
model.mySubSubClasses = [[new MySubClass()]];
85+
model.mySubSubClasses[0][0].name = "sub";
86+
model.mySubSubSubClasses = [[[new MySubClass()]]];
87+
model.mySubSubSubClasses[0][0][0].name = "sub";
88+
7989
return validator.validate(model).then(errors => {
80-
errors.length.should.be.equal(3);
90+
errors.length.should.be.equal(5);
8191

8292
errors[0].target.should.be.equal(model);
8393
errors[0].property.should.be.equal("title");
@@ -107,6 +117,47 @@ describe("nested validation", function () {
107117
subSubError.property.should.be.equal("name");
108118
subSubError.constraints.should.be.eql({minLength: "name must be longer than or equal to 5 characters"});
109119
subSubError.value.should.be.equal("my");
120+
121+
errors[3].target.should.be.equal(model);
122+
errors[3].property.should.be.equal("mySubSubClasses");
123+
errors[3].value.should.be.equal(model.mySubSubClasses);
124+
expect(errors[3].constraints).to.be.undefined;
125+
const subError3 = errors[3].children[0];
126+
subError3.target.should.be.equal(model.mySubSubClasses);
127+
subError3.value.should.be.equal(model.mySubSubClasses[0]);
128+
subError3.property.should.be.equal("0");
129+
const subSubError3 = subError3.children[0];
130+
subSubError3.target.should.be.equal(model.mySubSubClasses[0]);
131+
subSubError3.value.should.be.equal(model.mySubSubClasses[0][0]);
132+
subSubError3.property.should.be.equal("0");
133+
const subSubSubError3 = subSubError3.children[0];
134+
subSubSubError3.target.should.be.equal(model.mySubSubClasses[0][0]);
135+
subSubSubError3.property.should.be.equal("name");
136+
subSubSubError3.constraints.should.be.eql({minLength: "name must be longer than or equal to 5 characters"});
137+
subSubSubError3.value.should.be.equal("sub");
138+
139+
140+
errors[4].target.should.be.equal(model);
141+
errors[4].property.should.be.equal("mySubSubSubClasses");
142+
errors[4].value.should.be.equal(model.mySubSubSubClasses);
143+
expect(errors[4].constraints).to.be.undefined;
144+
const subError4 = errors[4].children[0];
145+
subError4.target.should.be.equal(model.mySubSubSubClasses);
146+
subError4.value.should.be.equal(model.mySubSubSubClasses[0]);
147+
subError4.property.should.be.equal("0");
148+
const subSubError4 = subError4.children[0];
149+
subSubError4.target.should.be.equal(model.mySubSubSubClasses[0]);
150+
subSubError4.value.should.be.equal(model.mySubSubSubClasses[0][0]);
151+
subSubError4.property.should.be.equal("0");
152+
const subSubSubError4 = subSubError4.children[0];
153+
subSubSubError4.target.should.be.equal(model.mySubSubSubClasses[0][0]);
154+
subSubSubError4.value.should.be.equal(model.mySubSubSubClasses[0][0][0]);
155+
subSubSubError4.property.should.be.equal("0");
156+
const subSubSubSubError4 = subSubSubError4.children[0];
157+
subSubSubSubError4.target.should.be.equal(model.mySubSubSubClasses[0][0][0]);
158+
subSubSubSubError4.property.should.be.equal("name");
159+
subSubSubSubError4.constraints.should.be.eql({minLength: "name must be longer than or equal to 5 characters"});
160+
subSubSubSubError4.value.should.be.equal("sub");
110161
});
111162
});
112163

0 commit comments

Comments
 (0)
Please sign in to comment.