typestack / class-validator

Decorator-based property validation for classes.
MIT License
11.03k stars 800 forks source link

fix: ValidatedNested with transform not validating #1473

Open ohad-griiip opened 2 years ago

ohad-griiip commented 2 years ago

Versions:

Description

Using Transform from class-transformer and ValidateNested of class-validator seems to skip validation of nested fields.

Minimal code-snippet showcasing the problem

import 'reflect-metadata'
import { IsInt, ValidateNested, validateSync } from 'class-validator'
import { plainToClass, Transform, Type } from 'class-transformer'

class Son {
    @IsInt()
    num!: number
}

class Parent {
    @ValidateNested()
    @Type(() => Son)
    @Transform(({ value }) => ({ num: 'a' }))
    son!: Son
}

const parent = plainToClass(Parent, { son: { num: 'abc' } })
console.log('errors: ', validateSync(parent).toString())  // Passing with no validation errors

Expected behavior

Displaying validation error - in this case not passing the isInt.

Actual behavior

Passing validation

ruscon commented 2 years ago

validation and transformations work with class instances.

try this

class Parent {
    @ValidateNested()
    @Type(() => Son)
    @Transform(({ value }) => {
      // change and return the current instance of `Son` or create new one
       value.num = 'a';
       return value;
    })
    son!: Son
}
molaeiali commented 2 years ago

Same issue here:

        "class-transformer": "^0.5.1",
        "class-validator": "^0.13.2",

I want to be able to have this input in my query params: :3000?sort={"name":1}&page=1&limit=20

class SortDto {
    @IsNumber()
    @IsIn([1, -1])
    readonly name!: number;
}

export class PaginateDto {
    @Transform(({ value }) => JSON.parse(value), { toClassOnly: true })
    @ValidateNested()
    readonly sort!: SortDto;

    @Type(() => Number)
    @IsOptional()
    @IsInt()
    @Min(0,)
    readonly page: number = 0;

    @Type(() => Number)
    @IsOptional()
    @IsInt()
    @Min(0)
    readonly limit: number = 10;
}

But when I pass anything as sort (even invalid values like { "name": "A" }, I'm receiving this in my controller:

{ page: 1, limit: 20, sort: {} }

I need to transform my sort variable from string to JSON as it is being passed through query params

molaeiali commented 2 years ago

validation and transformations work with class instances.

try this

class Parent {
    @ValidateNested()
    @Type(() => Son)
    @Transform(({ value }) => {
      // change and return the current instance of `Son` or create new one
       value.num = 'a';
       return value;
    })
    son!: Son
}

Tried

    @Transform(
        ({ value }) => {
            value = JSON.parse(value);
            return value;
        },
        { toClassOnly: true },
    )

No difference

manistra commented 2 years ago

For my particular issue I wanted to parse a string into an object and validate it... this works for me:

export class UploadDocumentDto {
  @ValidateNested()
  @Transform(({ value }) => plainToClass(UploadDocument, JSON.parse(value)), {
    toClassOnly: true,
  })
  @Type(() => UploadDocument)
  readonly documents: UploadDocument[];
}
export class UploadDocument {
  @IsNotEmpty()
  @IsString()
  id: string;

  @IsNotEmpty()
  @IsString()
  name: string;
}
manistra commented 2 years ago

validation and transformations work with class instances. try this

class Parent {
    @ValidateNested()
    @Type(() => Son)
    @Transform(({ value }) => {
      // change and return the current instance of `Son` or create new one
       value.num = 'a';
       return value;
    })
    son!: Son
}

Tried

  @Transform(
      ({ value }) => {
          value = JSON.parse(value);
          return value;
      },
      { toClassOnly: true },
  )

No difference

Instead of just: return value try using: return plainToClass(DesiredClassDto, value)

For some reason { toClassOnly: true } alone doesn't do the trick, and if I'm not wrong for values to be validated they need to be part of a dto class instance.

camargosproj commented 1 year ago

For my particular issue I wanted to parse a string into an object and validate it... this works for me:

export class UploadDocumentDto {
  @ValidateNested()
  @Transform(({ value }) => plainToClass(UploadDocument, JSON.parse(value)), {
    toClassOnly: true,
  })
  @Type(() => UploadDocument)
  readonly documents: UploadDocument[];
}
export class UploadDocument {
  @IsNotEmpty()
  @IsString()
  id: string;

  @IsNotEmpty()
  @IsString()
  name: string;
}

I had the same issue. This worked for me.

"class-validator": "^0.13.2" plainToClass is deprecated, so I used plainToInstance

Thanks a lot!

Tushar-zaapi commented 10 months ago

For my particular issue I wanted to parse a string into an object and validate it... this works for me:

export class UploadDocumentDto {
  @ValidateNested()
  @Transform(({ value }) => plainToClass(UploadDocument, JSON.parse(value)), {
    toClassOnly: true,
  })
  @Type(() => UploadDocument)
  readonly documents: UploadDocument[];
}
export class UploadDocument {
  @IsNotEmpty()
  @IsString()
  id: string;

  @IsNotEmpty()
  @IsString()
  name: string;
}

I had the same issue. This worked for me.

"class-validator": "^0.13.2" plainToClass is deprecated, so I used plainToInstance

Thanks a lot!

Yes working for me as well with plainToInstance. Thanks a lot both of you