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
相关产品推荐
相关产品推荐

