class-validator
class-validator copied to clipboard
fix: Class-validator combined to class-transformer
Description
When we combine class-validator
and class-transform
an error occurs when we try to validate a property (numberString in this case) and then transform it into a number, this happens no matter what order the decorators are.
Minimal code-snippet showcasing the problem
import { IsNotEmpty, IsNumberString, Min } from 'class-validator';
import { Transform } from 'class-transformer';
export class ExampleDto {
@IsNotEmpty()
@IsNumberString()
@Transform(p => +p.value)
@Min(1)
index: number;
}
Expected behavior
Validate that it is a number string, and then transform it into a number
Actual behavior
Actually in NestJS it produces an HTTP 400 error because the property is recognized as a number and not number string.
Anyone update on this having the same issue, I just want to validate the input before transformation.
I'm facing the same scenario and found this:
https://stackoverflow.com/questions/67920067/multiple-validation-pipes
https://stackoverflow.com/questions/69084933/nestjs-dto-class-set-class-validator-and-class-transformer-execution-order
Apparently that's desired and a workaround would be creating a separate pipe for transforming your dto.
Unless there's a better option, in that case I'm all ears.
any updates on this?
i'm facing the same issue, any update?
+1
I think this issue belongs in class-transformer
. Nevertheless, here is a solution to your problem, I think.
If I understand correctly, since class-validator
requires a class instance to run validation, you want to have the option to use class-transformer
to create a class instance without applying your transformation, then run validate
. After validation, you want to do your actual field transformation.
This is possible to achieve using TransformInstanceToInstance
Note: the docs on class-validator
are outdated. It uses the deprecated plainToClass
and TransformClassToClass
method and decorator names.
import { IsNotEmpty, IsNumberString, Min } from 'class-validator';
import { TransformInstanceToInstance } from 'class-transformer';
export class ExampleDto {
@IsNotEmpty()
@IsNumberString()
@TransformInstanceToInstance(p => +p.value)
@Min(1)
index: number | string;
}
I'm not sure if there is an obvious way to get around having a union type for the field for this specific example.
The sequence of operations for your use case would then look like this:
import { validate } from 'class-validator';
import { plainToInstance, instanceToInstance } from 'class-transformer';
const plainObject = { index: '2' }
// transform to an instance. Any `Transform` or `TransformPlainToInstance` decorators are applied
const initialInstance = plainToInstance(Customer, plainObject);
// run validation on our class instance
const errors = validate(initialInstance);
// perform an instance to instance transformation, applying `TransformInstanceToInstance` decorators
const transformedInstance = instanceToInstance(initialInstance);
If you don't want a class instance as your output, you can use TransformInstanceToPlain
and instanceToPlain
instead to achieve the same transformation, but end up with a plain object.
PS: I'm not on stackoverflow, so if anyone wants to share this solution to the relevant threads there, that would be great :)
@braaar
TransformInstanceToInstance
@TransformInstanceToInstance
doesn't work for me - it says for methods only (function / getter /setter) but not for properties. It's MethodDecorator
not PropertyDecorator
What you ask is not possible in one go. However, you can use groups
to your advantage and do the transformation and validation in two phases. An example:
import { IsNotEmpty, IsNumberString, Min, validate, validateSync } from 'class-validator';
import { plainToInstance, Transform, Type } from 'class-transformer';
export class ExampleDto {
@IsNotEmpty({ groups: ['first'] })
@IsNumberString({ groups: ['first'] })
@Transform(p => +p.value, { groups: ['typecast'] })
@Min(1, { groups: ['typecast'] })
index: number;
}
// First we just transform the object without changing the property type
const instanceOne = plainToInstance(ExampleDto, { index: '12' }, { groups: ['first'] });
const validationResultOne = validateSync(instanceOne, { groups: ['first'] })
console.log({ instanceOne, validationResultOne });
// { instanceOne: { index: "12", <prototype>: ExampleDto }, validationResultOne: [] }
// Then we transform the object again with type casting
const instanceTwo = plainToInstance(ExampleDto, instanceOne, { groups: ['typecast'] });
const validationResultTwo = validateSync(instanceTwo, { groups: ['typecast'] })
console.log({ instanceTwo, validationResultTwo });
// { instanceTwo: { index: 12, <prototype>: ExampleDto }, validationResultTwo: [] }
This has the disadvantage of running the transformation and validation twice for the two same payload.
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.