fix: @ValidateNested allows undefined
Description
When decorating a property with @ValidateNested, the validation of the class passes if undefined is passed as a value to that property. This is not expected behaviour, as all other validation functions require you to explicitly set the field as optional with @IsOptional.
Minimal code-snippet showcasing the problem
// A simple class with two properties, neither of which is marked as optional
class MyClass {
@ValidateNested()
@Type(() => MyNestedObject)
nested: MyNestedObject;
@IsString()
regularField: string;
}
// Case 1: omit the field with @ValidateNested() - validation passes
const obj = new MyClass();
obj.regularField = '';
const res = validateSync(obj); // This passes validation
// Case 2: omit the field with @IsString() - validation fails
const obj2 = new MyClass();
obj2.nested = new MyNestedObject();
const res = validateSync(obj2) // validation fails
Expected behavior
@ValidateNested should work like the other validation functions, where only explicitly setting the field as optional would allow passing an undefined value.
Actual behavior
@ValidateNested sees undefined values as valid (which is clearly also not an instance of MyNestedObject here). To achieve desired behaviour, I need to also add the @IsDefined decorator, which does not really make sense as for other validators I do not need to separately add that decorator.
Caveat:
I'm using this lib in the context of routing-controllers, so keep that in mind. This is how I assume classes are being constructed and validated from e.g. request bodies behind the scenes, but I may well be wrong. If this issue is only related to routing-controllers please inform me and I'll move my issue there.
In any case, there is earlier discussion about this in #83, which I feel was shut down without proper reasoning and marked as wontfix. The arguments (@NoNameProvided @MichalLytek) there were really quite semantic in nature, such as:
Yes, validation works only with instances of class decorated with constraints - for validating plain JS object I recommend you to use class-transformer-validator 😉
What issue? Your nested property has to be an instance of the class, not the plain object. Do it and it will be working fine 😉
But these are not really addressing the problem: if I define my validation rules as "I want to enforce that this property is an instance of class X", clearly undefined or a plain JS object are both values that should make the validation fail?
I also did not immediately see this behavior, but it can be solved using @IsNotEmptyObject()
Hey there, sorry for revive this here, but this issue is going to be addressed? I'm also having this behavior and it looks like @ValidateNested should already validate if the passed value is an instance of the type you're validating, and obviously, undefined is not an instance of it. Not providing the key annotated with @ValidateNested should have the same behavior as providing a wrong one unless @IsOptional is present
I'm also facing this issue with the following code:
class Address {
@IsString()
name: string;
}
class Shop {
@IsString()
name: string;
@ValidateNested()
@Type(() => Address)
address: Address;
}
I tried setting a default value for address but it didn't work:
class Shop {
@IsString()
name: string;
@ValidateNested()
@Type(() => Address)
address = {} as Address;
}
The only workaround I came up with was creating a new instance of the nested class as a default value:
class Shop {
@IsString()
name: string;
@ValidateNested()
@Type(() => Address)
address = new CreateAddressDto();
}
Just bumped onto this in my code. If ValidateNested does not check whether the property is undefined (regarding also the skipUndefinedProperties option), it should at least be mentioned in the documentation. This is a huge gotcha.
I can work on a fix or a documentation mention.
I resolved this issue by adding @IsObject({ message: '$property is required ' }) temporarily . I'm looking forward to the day when this problem can be resolved.