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

Node.js+Express+Formidable:嵌套对象文件上传解析异常修复

问题原因

Formidable 默认不会自动解析形如 users[0][photoFile] 的嵌套字段格式,返回的 fields 和 files 都是扁平化的键值对结构——直接展开合并这两个对象,得到的自然是字符串形式的键名,而非预期的嵌套对象/数组结构。


修复方案

需要手动实现扁平化键到嵌套结构的解析逻辑,再将文件数据合并到对应的嵌套位置中。

1. 实现嵌套结构解析工具函数

这个函数能把扁平化的键(如users[0][name]、users[0][photoFile])转换为嵌套的对象或数组:

/**
 * 将扁平化键值对解析为嵌套对象/数组
 * @param flatData 待解析的扁平化数据(fields 或 files)
 */
function parseNestedStructure(flatData: Record<string, any>): Record<string, any> {
  const result: Record<string, any> = {};

  Object.entries(flatData).forEach(([key, value]) => {
    // 拆分嵌套键的路径片段
    const pathSegments = key.match(/([^\[\]]+)(?:\[(\d+)\])?/g) || [];
    let currentNode = result;

    pathSegments.forEach((segment, index) => {
      const isLastSegment = index === pathSegments.length - 1;
      // 提取属性名和数组下标(如果有)
      const [propName, arrIndexStr] = segment.match(/([^\[]+)(?:\[(\d+)\])?/)!.slice(1);

      if (arrIndexStr !== undefined) {
        // 处理数组类型的嵌套
        const arrIndex = parseInt(arrIndexStr, 10);
        currentNode[propName] = currentNode[propName] || [];
        if (isLastSegment) {
          currentNode[propName][arrIndex] = value;
        } else {
          currentNode[propName][arrIndex] = currentNode[propName][arrIndex] || {};
          currentNode = currentNode[propName][arrIndex];
        }
      } else {
        // 处理对象属性类型的嵌套
        if (isLastSegment) {
          currentNode[propName] = value;
        } else {
          currentNode[propName] = currentNode[propName] || {};
          currentNode = currentNode[propName];
        }
      }
    });
  });

  return result;
}

2. 修改表单解析函数

更新parseFormData,先将fields和files解析为嵌套结构,再递归合并文件到对应字段位置:

public parseFormData = async <T>(
  request: Request,
  options: FormOptions = {
    multiples: false
  }
): Promise<T> => {
  const form = await this.getForm(request, options);
  return new Promise<T>((resolve, reject) => {
    form.parse(request, (err, fields, files) => {
      if (err) {
        if (err.code === FILE_TOO_LARGE_ERROR_CODE) {
          reject(new UploadMaxFileSizeException(`Upload file size limit exceeded.`));
        } else {
          reject(err);
        }
        return;
      }

      // 解析字段和文件为嵌套结构
      const nestedFields = parseNestedStructure(fields);
      const nestedFiles = parseNestedStructure(files);

      // 递归合并文件到字段的对应嵌套位置
      const mergeFilesToFields = (fieldsObj: any, filesObj: any) => {
        Object.entries(filesObj).forEach(([key, value]) => {
          if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
            fieldsObj[key] = fieldsObj[key] || {};
            mergeFilesToFields(fieldsObj[key], value);
          } else if (Array.isArray(value)) {
            fieldsObj[key] = fieldsObj[key] || [];
            value.forEach((fileItem, index) => {
              if (typeof fileItem === 'object' && fileItem !== null) {
                fieldsObj[key][index] = fieldsObj[key][index] || {};
                mergeFilesToFields(fieldsObj[key][index], fileItem);
              } else {
                fieldsObj[key][index] = fileItem;
              }
            });
          } else {
            fieldsObj[key] = value;
          }
        });
      };

      mergeFilesToFields(nestedFields, nestedFiles);
      resolve(nestedFields as T);
    });
  });
};

说明
  • 工具函数parseNestedStructure会自动识别数组下标(如[0])和对象属性,构建出符合预期的嵌套结构。
  • mergeFilesToFields会递归将文件数据注入到对应字段位置,确保每个user对象的photoFile字段正确关联上传的文件。
  • 如果开启multiples: true,函数也能自动处理多文件数组的嵌套解析。

内容的提问来源于stack exchange,提问作者Wai Yan Hein

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 05:40:26