FormData 传递 JSON 数据的问题解决

最近在开发中遇到了一个关于 FormData 传递 JSON 数据的问题,后端一直报 500 错误,但后端自己拼接的 curl 却能正常请求。经过一番排查,发现问题的根源在于 FormData 中 JSON 串的类型没对齐。本文记录整个排查过程和解决方案。

一、问题背景

1.1 接口需求

有一个接口需要同时传递两个参数:

  • 一个文件(File 类型)
  • 一段配置信息(JSON 格式)

1.2 常规做法

一般对于包含文件的请求,通常是用 FormData 包裹一下,具体流程是:

  1. 修改请求头中的 Content-Typemultipart/form-data
  2. 实例化一个 FormData
  3. 通过 append 方法添加文件和 JSON 数据

按照这个思路,我的代码如下:

javascript 复制代码
const formData = new FormData();
formData.append('file', file); // 文件
formData.append('req', JSON.stringify(reqParams)); // JSON 配置

二、问题现象

请求发送后,后端返回 500 同时出现如下报错:

bash 复制代码
HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported

错误大概的意思是后端不支持当前请求的 Content-Type,即后端期望接收 application/json 类型的数据,但实际收到的是 application/octet-stream(二进制流),导致无法正确解析。

三、排查过程

3.1 检查项目请求配置

首先怀疑是项目的请求处理有特殊配置或者是后端对于文件的处理存在问题,之前就遇到过包含 FormData 的接口生成 Sign 签名出错的问题。但测试后发现,单独传递文件是正常的 ,说明请求处理没有问题。然而,一旦添加了 JSON 参数,就会报 500 错误,这让我怀疑问题可能出在 JSON 数据的处理上。

3.2 报错搜索

我在网上搜了一下,找到了相同的报错:Spring/Postman Content type 'application/octet-stream' not supported。这个问题中提到了在 Postman 中可以通过设置字段的 Content-Type 来解决,这让我怀疑到问题可能还是跟 Content-Type 的设置有关。

3.3 对比 curl 字符串

参考上面的解决方案通过 Postman 拼接了一段可以正常请求的 curl 命令,然后再把前端请求出错的的 curl 命令进行对比后发现:

Postman 的 curl(能通)

bash 复制代码
curl -F "file=@test.jpg" -F "req={\"key\":\"value\"};type=application/json" ...

前端的 curl(报错)

bash 复制代码
curl -F "file=@test.jpg" -F "req={\"key\":\"value\"}" ...

关键差异在于:Postman 的 JSON 后面有 ;type=application/json 这正好和 Stack Overflow 上提到的 Content-Type 设置问题有关。

3.4 理解问题本质

multipart/form-data 格式中,每个字段都可以指定自己的 Content-Type。Postman 的 curl 中,req 字段明确指定了 type=application/json,所以后端能正确识别这是一个 JSON 数据。

而前端的请求中,req 字段没有指定 Content-Type,FormData 会将字符串类型的数据默认设置为 application/octet-stream(二进制流),导致后端无法正确解析。

FormData 的 append 方法支持三种类型:

  • 字符串 :会被当作普通文本,默认 Content-Type 为 application/octet-stream
  • Blob:可以自定义 Content-Type
  • File:继承自 Blob,有明确的文件类型

当我们使用 JSON.stringify() 后得到的是字符串,FormData 无法知道这是 JSON 格式,所以会导致后端报错。

四、知识点补充

为了更好的清楚问题的解决方案,我们先补充一些必要的知识点。

4.1 FormData API

FormData.append() 方法的用法:

javascript 复制代码
formData.append(name, value);
formData.append(name, value, filename); // 当 value 是 Blob 或 File 时

参数类型限制

  • value 只能是:stringBlob (包括子类型,如 File)
  • 字符串类型会被当作普通文本,无法指定 Content-Type

4.2 Blob 对象

Blob(Binary Large Object) 是 JavaScript 中表示二进制数据的对象。

构造函数

javascript 复制代码
new Blob(blobParts, options)

参数说明

  • blobParts:一个由 ArrayBuffer、ArrayBufferView、Blob、DOMString 等对象构成的数组
  • options:可选配置对象
    • type:MIME 类型字符串,如 'application/json''text/plain'

注意:第一个参数必须是数组,即使只有一个元素也要用数组包裹。

示例

javascript 复制代码
const blob = new Blob(['Hello, World!'], { type: 'text/plain' });
const jsonBlob = new Blob([JSON.stringify({ key: 'value' })], { type: 'application/json' });

4.3 multipart/form-data 中的 Content-Type

multipart/form-data 格式中,每个字段都可以有自己的 Content-Type。

格式示例

css 复制代码
Content-Disposition: form-data; name="key";filename="blob"
Content-Type: application/json

{...json data...}

Content-Type 在 curl 命令中,可以通过 ;type=application/json 来指定。后端框架(如 Spring Boot)在解析 multipart 数据时,会根据 Content-Type 来决定如何解析字段值,如果 Content-Type 不匹配,就会抛出 HttpMediaTypeNotSupportedException 异常。

五、解决方案

5.1 使用 Blob 包装 JSON 字符串

既然字符串无法指定 Content-Type,而 Blob 可以,那就用 Blob 来包装 JSON 字符串:

5.1.1 ❌ 报错方案:

javascript 复制代码
const formData = new FormData();
formData.append('req', JSON.stringify(reqParams));

在开发者工具中可以看到,使用字符串时,req 字段没有指定 Content-Type:

5.1.2 ✅ 正确方案:

javascript 复制代码
const formData = new FormData();
const reqBlob = new Blob([JSON.stringify(reqParams)], { type: 'application/json' });
formData.append('req', reqBlob);

使用 Blob 并指定 type: 'application/json' 后,req 字段的 Content-Type 正确设置为 application/json

5.3 完整示例

javascript 复制代码
async function uploadFileWithConfig(file, config) {
  const formData = new FormData();
  
  // 添加文件
  formData.append('file', file);
  
  // 添加 JSON 配置(使用 Blob)
  const configBlob = new Blob([JSON.stringify(config)], { type: 'application/json' });
  formData.append('req', configBlob);
  
  // 发送请求
  const response = await fetch('/api/upload', {
    method: 'POST',
    body: formData,
    // 可以不手动设置 Content-Type,浏览器会自动设置
  });
  
  return response.json();
}

六、总结

6.1 问题根源

问题的根源在于:FormData 中字符串类型的数据默认 Content-Type 为 字符串,而后端期望的是 application/json,导致类型不匹配。

6.2 解决方案

使用 Blob 对象 包装 JSON 字符串,并通过 type 属性指定 Content-Type 为 application/json,这样后端就能正确识别并解析 JSON 数据了。

6.3 经验教训

相关API的基础知识还需要加深理解,如 multipart/form-data 中可以单独设置 Content-Type,Blob可以指定设置JSON格式。

同时善用 AI 工具 在这次排查中起到了关键作用。刚出现问题的时候让 Cursor 解决,它只能反复地修改整个请求的 Content-Type,后来还是人工定位到是单个字段的 Content-Type 问题。最终通过询问 curl 中差异的 ;type=application/json 是什么意思,才引导 Cursor 给出了使用 Blob 的解决方案。这说明了解决问题时不能过多的依赖 Cursor 需要自己先排查问题引导 Cursor 向正确的方向排查解决。

参考资料

相关推荐
yuanyxh5 小时前
Mac 软件推荐
前端·javascript·程序员
万少5 小时前
AtomCode开发微信小程序《谁去呀》 全流程
前端·javascript·后端
某人辛木5 小时前
Web自动化测试
前端·python·pycharm·pytest
Kagol6 小时前
Superpowers GSD gstack AgentSkills深度测评
前端·人工智能
excel7 小时前
JavaScript 字符串与模板字面量:从表象到本质理解
前端
京东云开发者7 小时前
当AI成为导演-如何用AI创作动漫短剧
前端
李白的天不白8 小时前
使用 SmartAdmin 进行前后端开发
java·前端
乘风gg8 小时前
🤡PUA AI Coding 工具 的 10 条终极语录
前端·ai编程·claude
学Linux的语莫8 小时前
Vue 3 入门教程
前端·javascript·vue.js
怕浪猫8 小时前
第一章、Chrome DevTools Protocol (CDP) 详解
前端·javascript·chrome