This code:
import { IsNotEmpty, IsOptional, IsString, validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
const PATCH = 'patch';
const POST = 'post';
export class Test {
@IsOptional({ groups: [PATCH] })
@IsNotEmpty({ always: true })
@IsString()
name: string;
}
async function getValidationErrors(obj, group) {
return await validate(plainToClass(Test, obj), { groups: [group] });
}
describe('Test', () => {
it('should fail on post without name', async () => {
const errors = await getValidationErrors({}, POST);
expect(errors).not.toEqual([]);
});
it('should fail on post when name is undefined', async () => {
const errors = await getValidationErrors({ name: undefined }, POST);
expect(errors).not.toEqual([]);
});
it('should fail on post when name is null', async () => {
const errors = await getValidationErrors({ name: null }, POST);
expect(errors).not.toEqual([]);
});
it('should fail on post when name is empty', async () => {
const errors = await getValidationErrors({ name: '' }, POST);
expect(errors).not.toEqual([]);
});
it('should succeed on patch without name property', async () => {
const errors = await getValidationErrors({}, PATCH);
expect(errors).toEqual([]);
});
it('should fail on patch when name is undefined', async () => {
const errors = await getValidationErrors({ name: undefined }, PATCH);
expect(errors).not.toEqual([]);
});
it('should fail on patch when name is null', async () => {
const errors = await getValidationErrors({ name: null }, PATCH);
expect(errors).not.toEqual([]);
});
it('should fail on patch when name is empty', async () => {
const errors = await getValidationErrors({ name: '' }, PATCH);
expect(errors).not.toEqual([]);
});
});
gives the following results:

Am I missing something here?
Looking at the code of @IsOptional I see that it checks that the property is not undefined or null, that's why the test for empty string is not failing, but in theory shouldn't it check that this property exists on the object?
I do not understand your problem. I think your test cases are not good. You define property name as optional in case PATCH is in groups.
it('should fail on patch when name is undefined', async () => {
const errors = await getValidationErrors({ name: undefined }, PATCH);
expect(errors).not.toEqual([]);
});
it('should fail on patch when name is null', async () => {
const errors = await getValidationErrors({ name: null }, PATCH);
expect(errors).not.toEqual([]);
});
So Test should succeed on patch when name is undefined/null.
From docs:
@IsOptional() | Checks if given value is empty (=== null, === undefined) and if so, ignores all the validators on the property.
-- | --
I use nestjs to validate a DTO from my controller
I have this
class MyDTO {
@IsOptional()
@IsNotEmpty()
locale?: string
}
I send this body json
{
"locale": null
}
The expected behaviour:
The actual behaviour:
null is ignored.If I have this DTO
class MyDTO {
@IsNotEmpty()
locale?: string
}
Then null value throws CODE 400 which is expected.
@IsOptional() should ignores only when the key is not passed in the request (aka. undefined) or an option to not ignores null value.
I understand now. But as it is a breaking change I want to have more feedback from community.
I don't know if is the same issue, but i need to validate if a variable is notEmpty, notNull and notUndefined. Is that possible?
@vlapo How about something like an extra config option? The implementation I've got in my fork doesn't break backwards compatibility (as the option defaults to true, which is the default behaviour):
@isOptional({ nullable: false }) // object.hasOwnProperty(property);
@isOptional({ nullable: true }) // value !== undefined && value !== null
@isOptional() // value !== undefined && value !== null
The commit on the fork that adds this behaviour: https://github.com/se-internal/class-validator/commit/5ac9fa0c17ad43ecb0e8642675447b380a54139e
We would love this as well, ether with a nullable flag, or as a separate set of decorators (although naming them can be difficult).
@sam3d, your fork looks good to me, would you mind creating a PR?
Thanks @fhp! Yeah we had originally tried to create a new decorator and must've spent over an hour trying to come up with a good name. Eventually, we decided it made more sense to borrow the design pattern from typeorm's @ManyToOne() decorator (a nullable property that defaults to true).
I'll definitely put it into a PR at some point in the next couple of days! Just waiting until I have a moment to add tests. Confirmation from @vlapo or another maintainer that at least the design proposal is accepted would be amazing so I know I'm not doing it for nothing 馃槃
Any update for this? really want to see this feature in next release
@sam3d
I agree about the strange behavior of this.
I like the API proposed by @sam3d. We can use the current behavior as default and add a warning in the changelog about eventually being flipped.
I will take a crack at this at the weekend.
@NoNameProvided Any update? 10 days has been passed
How about keeping @IsOptional as-is and adding new decorators @IsNullable and @IsUndefinable.
Any support for this?
/**
* Skips validation if the target is null
*
* @example
* ```typescript
* class TestModel {
* @IsNullable({ always: true })
* big: string | null;
* }
* ```
*/
export function IsNullable(options?: ValidationOptions): PropertyDecorator {
return function IsNullableDecorator(prototype: object, propertyKey: string | symbol): void {
ValidateIf((obj): boolean => null !== obj[propertyKey], options)(prototype, propertyKey);
};
}
/**
* Skips validation if the target is undefined
*
* @example
* ```typescript
* class TestModel {
* @IsUndefinable({ always: true })
* big?: string;
* }
* ```
*/
export function IsUndefinable(options?: ValidationOptions): PropertyDecorator {
return function IsUndefinableDecorator(prototype: object, propertyKey: string | symbol): void {
ValidateIf((obj): boolean => undefined !== obj[propertyKey], options)(prototype, propertyKey);
};
}
Most helpful comment
@vlapo How about something like an extra config option? The implementation I've got in my fork doesn't break backwards compatibility (as the option defaults to
true, which is the default behaviour):The commit on the fork that adds this behaviour: https://github.com/se-internal/class-validator/commit/5ac9fa0c17ad43ecb0e8642675447b380a54139e