Currently it is very unhandy to set a default value for properties in case of a validation failure. You have to evaluate the ValidationError and set the default value on your own. It would be nice to have a new annotation, something like @Default('en-GB'), which allows the user to set a default value.
Instead of
@IsNotEmpty()
locale: string;
const localeErrors = validationErrors.find(error => error.property === 'locale');
if (localeErrors) {
initialQueryParameters.locale = 'en-GB';
}
you could simply write
@Default('en-GB')
@IsNotEmpty()
locale: string;
which sets a default value in case of an ocuring error.
Hi!
Have you tried adding it as a default value to the class? This passes for me:
class SomeClass {
@IsNotEmpty()
locale: string = 'en-GB';
}
const validationErrors = await validate(new SomeClass());
// validationErrors.length is 0
Hey,
the default value works in the example you've provided. The problem occurs by using class-validator in combination with other modules (in my case class-transformer). If the class-transformer writes an empty string to your _locale_ property, the validation afterwards fails (which is, of course, correct behavior). It would be great if the user could specify a default value for that scenario. In case the validation fails, the property will be set to its default value.
I know that this may be no real feature for class-validator because the functionality would be more than validating, but it may also save a lot of coding. Especially for poeple using the class-validator in combination with your class-transformer.
I think this should be fixed in class-validator then. It should not write an empty string to key if it doesn't exist in the source. The example below should work.
class SomeClass {
@IsNotEmpty()
locale: string = 'en-GB';
}
// it's a plain Object, without keys
const plain = {};
// it's an instance of SomeClass, with the value { locale: 'en-GB' }
// because locale was set in the constructor autmatically in the converted code
// and class-transformer should not remove it
const transformed = plainToClass(SomeClass, plain);
// there should be no validation errors, everything should pass
const validationErrors = await validate(transformed);
I have no time to check this, but if this doesn't work, then it will be fixed.
Your snippet works. It's not a validator bug. Rather, it is a feature that improves the functionality. Imagine something like the following, where @IsEnglishOrFrensh is a custom annotation which makes sure the given value is either 'english' or 'frensh'.
class SomeClass {
@IsNotEmpty()
locale: string = 'en-GB';
@IsNotEmpty()
@IsEnglishOrFrensh
language: string = 'english';
}
// it's a plain Object with empty locale and invalid language
const plain = { locale: '', language: 'german' };
const transformed = plainToClass(SomeClass, plain);
// there are two validation errors because locale 'en-GB' was overwritten with an empty
// value (it should be not empty) and language 'english' was overwritten with the invalid
// value 'german' (it should be either english or frensh).
const validationErrors = await validate(transformed);
If the default language and locale should be english, the user has to somehow check which fields caused an error and set the default values for each of them manually. Something like
if(validationErrors.find(error => error.property === 'locale')) {
transformed.locale = 'en-GB';
}
if(validationErrors.find(error => error.property === 'language')) {
transformed.language = 'english';
}
This could be a lot of effort and ugly looking code. Therefore, I propose an @Default annotation, which sets a default value in case of a validation error as described before.
I hope you understand my explanation; it's quite difficult to explain only by writing.
I still think things work here as expected. You provide an invalid value on purpose when you pass
{ locale: '', language: 'german' }
From the class-transformer point of view it does everything right:
From the class-validator point of view it also works:
locale property what should be not empty and passeslanguage propertyenglish of french and failsthe user has to somehow check which fields caused an error and set the default values for each of them manually
Your example there is not correct. You check for simply errors, but this will also fail when the user entered an invalid value. Or do you just override it to the default value on purpose? It would make sense to show the error instead.
So to sum it up, this functionality is not general and needed for your unique use-case, you want to assign a default value if the current value is an empty string, but what if others want to assign on undefined or null.
I advice you to rethink how you approach your issue with the lib current functionality in mind. You can also just create your own custom @Default decorator pretty simply if you want to continue with this approach.
For those interested; this is the decorator I quickly threw together using the Transform function.
import { Transform } from 'class-transformer';
const defaultValueDecorator = (defaultValue: any) => {
return Transform((target: any) => target || defaultValue);
};
export default defaultValueDecorator;
Example form my paginationQueryValidator (for lists)
@Exclude()
class PaginationQueryValidator {
@Expose()
@numeric()
@defaultValueDecorator(0)
public offset: number;
@Expose()
@numeric()
@defaultValueDecorator(20)
public limit: number;
}
export default PaginationQueryValidator;
Another approach is used in this answer in StackOverflow:
You can set default directly in your DTO class:
export class MyQuery {
readonly myQueryItem = 'mydefault';
}
You have to instantiate the class so that the default value is used. For that you can for example use the ValidationPipe with the option transform: true. If the value is set by your query params it will be overriden.
@Get()
@UsePipes(new ValidationPipe({ transform: true }))
getHello(@Query() query: MyQuery) {
return query;
}
With the setting transform: true it will automatically create an instance of your dto class.
I think this is more in class-transformer than class-validator scope. @NoNameProvided, @driescroons and @yuliankarapetkov pretty good describe how to handle this without @Default decorator. So lets close this issue. Thank you all for comments.
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Most helpful comment
For those interested; this is the decorator I quickly threw together using the Transform function.
Example form my paginationQueryValidator (for lists)