Typescript 转换多部分/表单数据请求主体,然后使用validationPipe

Typescript 转换多部分/表单数据请求主体,然后使用validationPipe,typescript,transform,nestjs,class-validator,Typescript,Transform,Nestjs,Class Validator,我正在尝试使用transform将formData requert从字符串转换为json对象,然后使用validationPipe(类验证器)进行验证,但我得到了 Maximum call stack size exceeded at cloneObject (E:\projectos\Gitlab\latineo\latineo-apirest\node_modules\mongoose\lib\utils.js:290:21) at clone (E:\projectos\G

我正在尝试使用transform将formData requert从字符串转换为json对象,然后使用validationPipe(类验证器)进行验证,但我得到了

Maximum call stack size exceeded
    at cloneObject (E:\projectos\Gitlab\latineo\latineo-apirest\node_modules\mongoose\lib\utils.js:290:21)
    at clone (E:\projectos\Gitlab\latineo\latineo-apirest\node_modules\mongoose\lib\utils.js:204:16)
尝试调试后,我进入我的控制器3次,对象保存在数据库中,但没有验证和内部转换JSONTOOBJECT 9次

我的主菜

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({ transform: true }));
  app.use(helmet());
  app.enableCors();
  app.use(
    rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 4000, // limit each IP to 100 requests per windowMs
    }),
  );
  app.use(compression());
  app.use('/upload', express.static(join(__dirname, '..', 'upload')));
  const options = new DocumentBuilder()
    .setTitle('XXX')
    .setDescription('XXX')
    .setVersion('1.0')
    .addBearerAuth()
    .build();
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('api', app, document);
  await app.listen(3000);
}
bootstrap();
我的nestjs dto

export class CreateRestaurantDto {
  // to do, check the documentation from class-validator for array of objects
  @IsString()
  @IsNotEmpty()
  @ApiModelProperty({ type: String })
  @Length(3, 100)
  readonly name: string;
  @IsString()
  @IsNotEmpty()
  @ApiModelProperty({ type: String })
  @Length(3, 500)
  readonly description: string;
  @Transform(transformJSONToObject, { toClassOnly: true })
  @ValidateNested()
  @ApiModelProperty({ type: [RestaurantsMenu] })
  readonly menu: RestaurantsMenu[];
  @Transform(transformJSONToObject, { toClassOnly: true })
  @IsString({
    each: true,
  })
  @IsNotEmpty({
    each: true,
  })
  @Length(3, 50, { each: true })
  @ApiModelProperty({ type: [String] })
  readonly type: string[];
  @Transform(transformJSONToObject, { toClassOnly: true })
  @ValidateNested()
  @ApiModelProperty({ type: [RestaurantsLocation] })
  readonly location: RestaurantsLocation[];
}
这是我的控制器

 @ApiBearerAuth()
  @UseGuards(JwtAuthGuard)
  @UseInterceptors(FilesInterceptor('imageUrls'))
  @ApiConsumes('multipart/form-data')
  @ApiImplicitFile({
    name: 'imageUrls',
    required: true,
    description: 'List of restaurants',
  })
  @Post()
  async createRestaurant(
    @Body() createRestaurantDto: CreateRestaurantDto,
    @UploadedFiles() imageUrls,
    @Req() request: any,
  ): Promise<RestaurantDocument> {
    const userId = request.payload.userId;
    const user = await this.usersService.findUserById(userId);
    const mapUrls = imageUrls.map(element => {
      return element.path;
    });
    const restaurant = {
      ...createRestaurantDto,
      imagesUrls: mapUrls,
      creator: user,
    };
 // just add a resturant to mongodb
    const createdRestaurant = await this.restaurantsService.addRestaurant(
      restaurant,
    );
    user.restaurants.push(createdRestaurant);
    user.save();
    return createdRestaurant;
  }
@apibarerauth()
@UseGuards(JwtAuthGuard)
@UseInterceptors(FileInterceptor('imageUrls'))
@ApiConsumes(“多部分/表单数据”)
@apimplicitfile({
名称:“imageUrls”,
要求:正确,
描述:'餐厅列表',
})
@Post()
异步创建餐厅(
@Body()createRestaurantTo:createRestaurantTo,
@UploadedFiles()图像URL,
@请求:任何,
):承诺{
const userId=request.payload.userId;
const user=wait this.usersService.finduserbyd(userId);
常量mapUrls=imageUrls.map(元素=>{
返回元素.path;
});
康斯特餐厅={
…CreateRestaurantTo,
imagesUrls:mapUrls,
创建者:用户,
};
//只需在mongodb中添加一家餐厅即可
const createdRestaurant=等待this.restaurantsService.addRestaurant(
餐厅,
);
用户.餐厅.推送(createdRestaurant);
user.save();
返回createdRestaurant;
}

我知道现在已经太晚了:)但也许这可以帮助别人

借助深度解析json,我的解决方案是创建一个
ParseFormDataJsonPipe
,如下所示,并将其放在
ValidationPipe
之前。 您可以在构造函数中传递表单数据属性的例外列表,以保留一些纯粹的内容,例如图像、二进制、jsonArray

从'@nestjs/common'导入{PipeTransform,ArgumentMetadata};
从“deep parse json”导入{deepParseJson};
从“lodash”导入*as uuu;
类型TParseFormDataJsonOptions={
除了?:字符串[];
};
导出类ParseFormDataJsonPipe实现PipeTransform{
构造函数(私有选项?:TParseFormDataJsonOptions){}
转换(值:任意,_元数据:ArgumentMetadata){
const{except}=this.options;
const serializedValue=值;
常量originProperties={};
如果(除了?长度){
_.merge(originProperties,uu.pick(serializedValue,…除外));
}
const deserializedValue=deepParseJson(值);
log(`deserializedValue`,deserializedValue);
返回{…反序列化的值,…originProperties};
}
}
下面是控制器的一个示例

@Post()
@apibarerauth('admin')
@ApiConsumes(“多部分/表单数据”)
@UseGuard(JwtAuthGuard、RoleGuard)
@角色(Role.Admin)
@使用拦截器(
FileInterceptor('图像'{
限制:{fileSize:imgurConfig.fileSizeLimit},
}),
)
异步createProductByAdmin(
@身体(
新的ParseFormDataJsonPipe({除了:['image','categoryId']}),
新建ValidationPipe(),
)
createDto:CreateProductDto,
@UploadedFile()图像:Express.Multer.File,
) {
log(`createDto`,createDto);
}

我知道现在已经太晚了:)但也许这可以帮助别人

借助深度解析json,我的解决方案是创建一个
ParseFormDataJsonPipe
,如下所示,并将其放在
ValidationPipe
之前。 您可以在构造函数中传递表单数据属性的例外列表,以保留一些纯粹的内容,例如图像、二进制、jsonArray

从'@nestjs/common'导入{PipeTransform,ArgumentMetadata};
从“deep parse json”导入{deepParseJson};
从“lodash”导入*as uuu;
类型TParseFormDataJsonOptions={
除了?:字符串[];
};
导出类ParseFormDataJsonPipe实现PipeTransform{
构造函数(私有选项?:TParseFormDataJsonOptions){}
转换(值:任意,_元数据:ArgumentMetadata){
const{except}=this.options;
const serializedValue=值;
常量originProperties={};
如果(除了?长度){
_.merge(originProperties,uu.pick(serializedValue,…除外));
}
const deserializedValue=deepParseJson(值);
log(`deserializedValue`,deserializedValue);
返回{…反序列化的值,…originProperties};
}
}
下面是控制器的一个示例

@Post()
@apibarerauth('admin')
@ApiConsumes(“多部分/表单数据”)
@UseGuard(JwtAuthGuard、RoleGuard)
@角色(Role.Admin)
@使用拦截器(
FileInterceptor('图像'{
限制:{fileSize:imgurConfig.fileSizeLimit},
}),
)
异步createProductByAdmin(
@身体(
新的ParseFormDataJsonPipe({除了:['image','categoryId']}),
新建ValidationPipe(),
)
createDto:CreateProductDto,
@UploadedFile()图像:Express.Multer.File,
) {
log(`createDto`,createDto);
}

const createdRestaurant=等待这个.restaurantService.createRestaurant(…)您使用的是无条件递归。你不停地调用
createRestaurant
,没有退出条件。为什么我需要一个没有转换的条件,只要传递一个就行了time@Train被调用的
createRestaurant
是来自
RestaurantService
类的。虽然我承认在控制器和服务中使用相同的方法名可能会有点混淆。是的,我将进行编辑以避免这种情况。
const createdRestaurant=wait this.restaurantsService.createRestaurant(…)您使用的是无条件递归。你不停地调用
createRestaurant
,没有退出条件。为什么我需要一个没有转换的条件,只要传递一个就行了time@Train被调用的
createRestaurant
是来自
RestaurantService
类的。虽然我承认,在控制器和服务中使用相同的方法名可能会有点混乱。ye,就是这样,我将进行编辑以避免出现这种情况。