使用类验证器验证嵌套的 DTO 对象

use*_*338 4 javascript dto node.js express class-validator

我正在尝试使用类验证器来验证传入的数据。数据由对象数组组成。每个对象都应该经过验证。

我面临的问题是,当所有内容都正确输入时,我不断收到错误。似乎正在检查父类及其子类的属性,因此whitelistValidation子类的每个属性都会抛出错误。

这是正在生成的错误:

[
   {
      "target":{
         "drainPoints":[
            {
               "drainPointType":"roundsurface",
               "flowType":"normal",
               "flowCoefficient":0.5,
               "point":{
                  "x":0,
                  "y":0
               }
            }
         ]
      },
      "value":[
         {
            "drainPointType":"roundsurface",
            "flowType":"normal",
            "flowCoefficient":0.5,
            "point":{
               "x":0,
               "y":0
            }
         }
      ],
      "property":"drainPoints",
      "children":[
         {
            "target":[
               {
                  "drainPointType":"roundsurface",
                  "flowType":"normal",
                  "flowCoefficient":0.5,
                  "point":{
                     "x":0,
                     "y":0
                  }
               }
            ],
            "value":{
               "drainPointType":"roundsurface",
               "flowType":"normal",
               "flowCoefficient":0.5,
               "point":{
                  "x":0,
                  "y":0
               }
            },
            "property":"0",
            "children":[
               {
                  "target":{
                     "drainPointType":"roundsurface",
                     "flowType":"normal",
                     "flowCoefficient":0.5,
                     "point":{
                        "x":0,
                        "y":0
                     }
                  },
                  "value":"roundsurface",
                  "property":"drainPointType",
                  "constraints":{
                     "whitelistValidation":"property drainPointType should not exist"
                  }
               },
               {
                  "target":{
                     "drainPointType":"roundsurface",
                     "flowType":"normal",
                     "flowCoefficient":0.5,
                     "point":{
                        "x":0,
                        "y":0
                     }
                  },
                  "value":"normal",
                  "property":"flowType",
                  "constraints":{
                     "whitelistValidation":"property flowType should not exist"
                  }
               },
               {
                  "target":{
                     "drainPointType":"roundsurface",
                     "flowType":"normal",
                     "flowCoefficient":0.5,
                     "point":{
                        "x":0,
                        "y":0
                     }
                  },
                  "value":0.5,
                  "property":"flowCoefficient",
                  "constraints":{
                     "whitelistValidation":"property flowCoefficient should not exist"
                  }
               },
               {
                  "target":{
                     "drainPointType":"roundsurface",
                     "flowType":"normal",
                     "flowCoefficient":0.5,
                     "point":{
                        "x":0,
                        "y":0
                     }
                  },
                  "value":{
                     "x":0,
                     "y":0
                  },
                  "property":"point",
                  "constraints":{
                     "whitelistValidation":"property point should not exist"
                  }
               }
            ]
         }
      ]
   }
]
Run Code Online (Sandbox Code Playgroud)

包含数组的 DTO 对象:

export class CreateDrainPointDTO extends DTO {
  @IsArray()
  @IsNotEmpty()
  @ArrayMinSize(1)
  @ValidateNested({ each: true })
  @Type(() => DrainPointDTO)
    drainPoints: DrainPoint[]
}
Run Code Online (Sandbox Code Playgroud)

对象本身:

export class DrainPointDTO {
  @IsString()
  @IsOptional()
    uuid: string

  @IsEnum(DrainPointType)
  @IsNotEmpty()
    drainPointType: DrainPointType

  @IsEnum(DrainPointflowType)
  @IsNotEmpty()
    flowType: DrainPointflowType

  @IsArray()
  @IsNotEmpty()
   point: Point

  @IsNumber()
  @IsOptional()
    flowCoefficient: number
}
Run Code Online (Sandbox Code Playgroud)

我的自定义 DTO 抽象类:

export abstract class DTO {
  static async factory<T extends DTO>(Class: new () => T, partial: Partial<T>): Promise<T> {
    const dto = Object.assign(new Class(), partial)

    const errors = await validate(dto, { whitelist: true, forbidNonWhitelisted: true })

    if (errors.length > 0) {
      throw new CustomError()
        .withError('invalid_parameters')
        .withValidationErrors(errors)
    }

    return dto
  }
}
Run Code Online (Sandbox Code Playgroud)

我使用这个 DTO 抽象类是为了有一种干净的方法来检查控制器内的主体:

  async createDrainPoint (req: Request, res: Response): Promise<void> {
    const dto = await DTO.factory(CreateDrainPointDTO, req.body as Partial<CreateDrainPointDTO>)

    const drainPoints = await this.drainPointService.create(dto)

    res.status(201).json(DrainPointTransformer.array(drainPoints))
  }
Run Code Online (Sandbox Code Playgroud)

eri*_*2k8 6

问题在于,创建数据的方式实际上并不以DrainPointDTO数组中的实例结束,而只是符合其形状的对象。您可以通过以下内容看到这一点:

async createDrainPoint (req: Request, res: Response): Promise<void> {
  const dto = await DTO.factory(CreateDrainPointDTO, req.body as Partial<CreateDrainPointDTO>)

  console.log(dto.drainPoints[0].constructor);
Run Code Online (Sandbox Code Playgroud)

它将输出[Function: Object], 而不是[class DrainPoint].

您正在创建 的实例CreateDrainPointDTO,并Object.assign填充值,但它不会将任何嵌套值映射到类实例。它在功能上与以下相同:

const dto = new CreateDrainPointDTO()

dto.drainPoints = [
  {
    uuid: 'some-id',
    drainPointType: DrainPointType.roundsurface,
    flowType: DrainPointflowType.normal,
    flowCoefficient: 0.5,
    point: {
      x: 0,
      y: 0,
    },
  },
]

// outputs '[Function: Object]', NOT '[class DrainPoint]'
console.log(dto.drainPoints[0].constructor)
Run Code Online (Sandbox Code Playgroud)

由于所有装饰器都是DrainPointDTO类的一部分,因此它无法弄清楚如何验证它。通常与类转换器class-validator结合使用(我假设您已经拥有它,因为它是装饰器的来源)。它将创建嵌套类的实例(只要它们在 中指定了类,您已经这样做了)。对于您的工厂,您可以这样做:Type@Type

import { plainToInstance } from 'class-transformer';

// ...
const dto = plainToInstance(Class, partial);
Run Code Online (Sandbox Code Playgroud)

注意:您可能不希望工厂接受 a Partial<T>,而只是接受T。如果您用来初始化类的值缺少任何必需的值,那么它无论如何都不会有效。这对于 来说并不重要req.body,因为无论如何它都是一种类型any,但是如果您要使用它以编程方式创建一个,它会给您静态检查缺失的字段。

  • 我试图在答案中保留意见,但这是我认为装饰类是表示有效负载的糟糕机制的众多原因之一。我们使用 JSON 作为有效负载,因为它具有可读性,并且允许我们以与其结构相同的形式传输数据。JSON =“JavaScript 对象表示法”,因此我们在序列化有效负载中使用 JavaScript 表示法,但不在我们的... JavaScript 中使用 JavaScript 表示法。:-/ (2认同)