You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在NestJs中验证FormData提交的对象数组?

NestJS 接口接收FormData中对象数组的问题

服务器端代码

接口定义

@Post()
@UseInterceptors(
  FileFieldsInterceptor([
    { name: 'settingsFile', maxCount: 1 },
    { name: 'imageFile', maxCount: 1}
  ])
)
async create(
  @Body() createCaptionDto: CreatePostDto,
  @UploadedFiles()
  files: {
    settingsFile: Express.Multer.File[];
    imageFile: Express.Multer.File[];
  }
) {...

DTO定义

export class CreatePostDto {
    ...

    @IsArray()
    @ArrayMinSize(1)
    // @ValidateNested({ each: true })
    // @Type(() => PostSource)
    sources: Array<PostSource>;

    @IsArray()
    @IsString({ each: true })
    tags: Array<string>;
}

前端调用问题

前端使用fetch+FormData传输数据时,字符串数组tags通过循环append('tags[]', tag)可正常解析,但对象数组sources的多种尝试均无法正确反序列化为对象数组:

  1. 尝试一:
formData.append('sources[]', JSON.stringify(sources));

服务器解析结果:

['[{attr1: 223, attr2: 3737}, {attr1: 235325, attr2: 5366}]']

字符串化的数组被额外包裹一层数组,不符合预期。

  1. 尝试二:
for (const source of createCaptionDto.sources) {
    formData.append('sources[]', JSON.stringify(source));
}

服务器解析结果:

['{attr1: 223, attr2: 3737}', '{attr1: 235325, attr2: 5366}']

得到的是字符串数组而非对象数组。

  1. 尝试三:
formData.append('sources', JSON.stringify(sources));

NestJS无法识别该字段为数组类型。


解决办法

方案1:启用DTO嵌套验证+拆分属性传参

首先在CreatePostDto中启用嵌套验证(取消之前注释的代码):

export class CreatePostDto {
    ...

    @IsArray()
    @ArrayMinSize(1)
    @ValidateNested({ each: true }) // 启用嵌套对象验证
    @Type(() => PostSource) // 指定嵌套对象的类型
    sources: Array<PostSource>;

    @IsArray()
    @IsString({ each: true })
    tags: Array<string>;
}

前端传输时,将对象的每个属性按sources[index].property的格式单独添加到FormData:

createCaptionDto.sources.forEach((source, index) => {
    // 示例:假设PostSource包含attr1、attr2两个属性
    formData.append(`sources[${index}].attr1`, source.attr1);
    formData.append(`sources[${index}].attr2`, source.attr2);
});

这种方式下,NestJS的class-validator和class-transformer会自动将参数解析为PostSource对象数组。

方案2:自定义管道处理字符串反序列化

保留前端尝试二的传参方式,在服务器端添加自定义管道解析字符串为对象:

  1. 创建ParseJsonArrayPipe管道:
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';

@Injectable()
export class ParseJsonArrayPipe implements PipeTransform {
  transform(value: any) {
    if (!Array.isArray(value)) {
      throw new BadRequestException('sources必须为数组格式');
    }
    try {
      return value.map(item => JSON.parse(item));
    } catch (error) {
      throw new BadRequestException('sources数组中存在无效JSON格式的元素');
    }
  }
}
  1. 在接口中使用该管道:
async create(
  @Body('sources', new ParseJsonArrayPipe()) sources: PostSource[],
  @Body() createCaptionDto: Omit<CreatePostDto, 'sources'>,
  @UploadedFiles() files: { ... }
) {
  const fullDto = { ...createCaptionDto, sources };
  // 后续业务逻辑
}

也可以直接在DTO的sources字段上使用@Transform装饰器简化处理:

import { Transform } from 'class-transformer';

export class CreatePostDto {
    ...

    @IsArray()
    @ArrayMinSize(1)
    @Transform(({ value }) => value.map(item => JSON.parse(item)))
    sources: Array<PostSource>;

    ...
}

方案3:将非文件数据作为单个JSON字段传输

前端把除文件外的所有数据序列化为JSON字符串,作为单个字段传入FormData:

// 把整个DTO序列化为JSON字符串
formData.append('payload', JSON.stringify(createCaptionDto));
// 单独添加文件
formData.append('settingsFile', settingsFile);
formData.append('imageFile', imageFile);

服务器端调整接收逻辑:

async create(
  @Body('payload') payload: string,
  @UploadedFiles() files: { ... }
) {
  const createCaptionDto = JSON.parse(payload) as CreatePostDto;
  // 若需要验证,可手动调用class-validator的validate方法
  // 后续业务逻辑
}

这种方式无需修改DTO验证规则,实现简单直接,但需手动处理JSON解析和可能的参数验证。


内容的提问来源于stack exchange,提问作者HSE

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 04:52:47