JavaScript将表单数据序列化为JSON格式的方法

作者:像素猫

时间:2026-11-13

浏览:116

详细讲解在浏览器中使用JavaScript将HTML表单数据序列化为JSON格式的方法,涵盖FormData基础用法、多值字段处理、form.elements类型转换及常见注意事项。

文章目录

在浏览器中用 JavaScript 将 HTML 表单数据序列化为 JSON 字符串,最直接的做法是先用 new FormData(form) 收集表单控件的键值对,再把键值对转为普通 JavaScript 对象,最后调用 JSON.stringify() 生成 JSON 字符串。当表单包含多选框、同名多值字段,或需要保留数字与布尔类型时,则需要在此基础上按规则聚合数组或直接遍历 form.elements。

JavaScript将表单数据序列化为JSON格式的方法
图 0:将浏览器表单数据转换为 JSON 字符串的四阶段核心路径

一、基础方法:FormData 配合 Object.fromEntries

对于大多数只包含单值字段(如文本输入框、密码框、单选按钮、单选下拉框)的表单,浏览器内置的 FormData 和 Object.fromEntries() 就能完成转换,不需要手动逐个读取 DOM 元素的 value。

实际操作时需要注意三个前提条件,它们直接决定了字段能否进入最终的 JSON:

  • 控件必须设置 name 属性,FormData 会把 name 作为键名,只有 id 没有 name 的控件会被忽略。
  • 处于 disabled 状态的控件不会被 FormData 收集;若需要不可编辑但仍参与提交,应使用 readonly。
  • 未勾选的复选框和未选中的单选框不会出现在 FormData 中。

下面是一个可以直接在浏览器 HTML 页面中运行的完整示例。假设页面上有一个用户注册表单,在提交事件中阻止默认整页跳转,然后把表单序列化为 JSON 字符串:

<form id="userForm">
  <label>用户名:<input type="text" name="username" value="zhangsan" /></label>
  <label>邮箱:<input type="email" name="email" value="zhangsan@example.com" /></label>
  <label>部门:
    <select name="department">
      <option value="dev" selected>研发部</option>
      <option value="ops">运维部</option>
    </select>
  </label>
  <button type="submit">提交</button>
</form>

<script>
  const userForm = document.getElementById('userForm');

  userForm.addEventListener('submit', function (event) {
    event.preventDefault();

    const formData = new FormData(userForm);
    const payload = Object.fromEntries(formData.entries());
    const jsonString = JSON.stringify(payload);

    console.log(jsonString);
  });
</script>

点击提交按钮后,在浏览器开发者工具的控制台中会看到如下结果(示例输出):

// 示例输出
{"username":"zhangsan","email":"zhangsan@example.com","department":"dev"}

FormData 实例本身实现了可迭代协议,formData.entries() 会逐条产出 [key, value] 二元组,因此 Object.fromEntries(formData) 与 Object.fromEntries(formData.entries()) 效果相同。为了代码意图清晰,保留 .entries() 更便于阅读。

JavaScript将表单数据序列化为JSON格式的方法
图 1:使用 FormData 与 Object.fromEntries 将单值表单序列化为 JSON 的操作步骤

二、处理多值字段:复选框组与多选下拉框

Object.fromEntries() 有一个很容易忽略的限制:当表单里存在多个相同 name 的值时,例如多选复选框(<input type="checkbox" name="hobby">)或开启了 multiple 属性的 <select>,后出现的同名键会直接覆盖前面出现的值,最终只剩下最后勾选的那一项。

如果表单存在多值字段,就不要直接调用 Object.fromEntries(),而应遍历 FormData,在键名第一次出现时存成单值,再次出现时合并为数组;或者对已知的多值字段显式使用 formData.getAll(key) 保证它始终输出数组(即使只勾选了一项)。

JavaScript将表单数据序列化为JSON格式的方法
图 2:多值表单字段在 Object.fromEntries 与聚合处理下的输出对比

下面写一个通用的辅助函数 serializeFormToJson,先对重复键自动聚合成数组,同时允许通过第二个参数传入“始终按数组处理”的字段名列表(防止用户只勾选一个复选框时字段从数组变成字符串):

<form id="profileForm">
  <input type="text" name="nickname" value="林青" />
  <label><input type="checkbox" name="hobby" value="reading" checked /> 阅读</label>
  <label><input type="checkbox" name="hobby" value="coding" checked /> 编程</label>
  <label><input type="checkbox" name="hobby" value="music" /> 音乐</label>
  <select name="skills" multiple>
    <option value="js" selected>JavaScript</option>
    <option value="css">CSS</option>
  </select>
  <button type="submit">保存</button>
</form>

<script>
  function serializeFormToJson(form, arrayFields = []) {
    const formData = new FormData(form);
    const payload = {};
    const arrayFieldSet = new Set(arrayFields);

    for (const [key, value] of formData.entries()) {
      if (arrayFieldSet.has(key)) {
        if (!Array.isArray(payload[key])) {
          payload[key] = [];
        }
        payload[key].push(value);
        continue;
      }

      if (Object.prototype.hasOwnProperty.call(payload, key)) {
        if (!Array.isArray(payload[key])) {
          payload[key] = [payload[key]];
        }
        payload[key].push(value);
      } else {
        payload[key] = value;
      }
    }

    for (const key of arrayFieldSet) {
      if (!Object.prototype.hasOwnProperty.call(payload, key)) {
        payload[key] = [];
      }
    }

    return JSON.stringify(payload);
  }

  const profileForm = document.getElementById('profileForm');
  profileForm.addEventListener('submit', function (event) {
    event.preventDefault();
    const jsonString = serializeFormToJson(profileForm, ['hobby', 'skills']);
    console.log(jsonString);
  });
</script>

上述代码提交后的结果如下(示例输出):

// 示例输出
{"nickname":"林青","hobby":["reading","coding"],"skills":["js"]}

即使 skills 只选中了一项,或者 hobby 一项都没选,由于 arrayFields 指定了这两个字段名,输出依然能保持一致的数组结构 ["js"] 或 [],后端反序列化时不容易因类型变化而报错。

三、保留数字、布尔值与未勾选状态:遍历 form.elements

FormData 还有一个本质特点:所有非文件字段拿到的值都是字符串。例如 <input type="number" name="age" value="25"> 取出来的是 "25",单个用来表示开关的复选框(如“同意协议”)在未勾选时甚至不会出现在 FormData 里。

如果你的接口严格区分字符串、数字和布尔类型,或者需要把未勾选的单个复选框明确序列化为 false,直接遍历原生表单的 form.elements 集合会更合适。

JavaScript将表单数据序列化为JSON格式的方法
图 3:常见表单控件在 FormData 与 form.elements 两种方式下的取值差异

遍历 form.elements 时要格外留意三类不需要进入 JSON 的元素:没有 name 的元素、设置了 disabled 的元素,以及提交按钮(submit)、普通按钮(button)、重置按钮(reset)和 <fieldset> 容器。另外,单选按钮组(radio)在遍历时会遇到多个同名控件,只能写入 el.checked 为 true 的那一个。

下面是一个处理单选框、单个开关复选框、多选复选框组、数字输入框以及多选下拉框的完整示例:

<form id="orderForm">
  <input type="text" name="product" value="机械键盘" />
  <input type="number" name="quantity" value="2" />
  <label><input type="radio" name="payType" value="online" checked /> 在线支付</label>
  <label><input type="radio" name="payType" value="cod" /> 货到付款</label>
  <label><input type="checkbox" name="needInvoice" /> 需要发票</label>
  <button type="submit">下单</button>
</form>

<script>
  function serializeElementsToJson(form) {
    const payload = {};
    const elements = Array.from(form.elements);

    for (const el of elements) {
      if (!el.name || el.disabled) continue;
      if (['button', 'submit', 'reset', 'fieldset', 'file'].includes(el.type)) continue;

      if (el.type === 'radio') {
        if (el.checked) {
          payload[el.name] = el.value;
        }
        continue;
      }

      if (el.type === 'checkbox') {
        // 判断表单中是否存在同名的多个复选框
        const sameNameCheckboxes = form.querySelectorAll(
          `input[type="checkbox"][name="${CSS.escape(el.name)}"]`
        );
        if (sameNameCheckboxes.length === 1) {
          payload[el.name] = el.checked;
        } else {
          if (!Array.isArray(payload[el.name])) {
            payload[el.name] = [];
          }
          if (el.checked) {
            payload[el.name].push(el.value);
          }
        }
        continue;
      }

      if (el.type === 'select-multiple') {
        payload[el.name] = Array.from(el.selectedOptions).map((opt) => opt.value);
        continue;
      }

      if (el.type === 'number') {
        payload[el.name] = el.value === '' ? null : el.valueAsNumber;
        continue;
      }

      payload[el.name] = el.value;
    }

    return JSON.stringify(payload);
  }

  const orderForm = document.getElementById('orderForm');
  orderForm.addEventListener('submit', function (event) {
    event.preventDefault();
    console.log(serializeElementsToJson(orderForm));
  });
</script>

未勾选“需要发票”直接提交时,控制台中会输出如下内容(示例输出):

// 示例输出
{"product":"机械键盘","quantity":2,"payType":"online","needInvoice":false}

可以看到,quantity 保持了数字类型 2,未勾选的 needInvoice 也明确记录为布尔值 false。

四、把序列化后的 JSON 发送到服务端

把表单数据序列化为 JSON 字符串后,通常需要配合 fetch() 发送给后端接口。这里最容易漏掉的是请求头中的 Content-Type:如果不显式指定 application/json,fetch 在发送字符串时默认使用 text/plain;charset=UTF-8,很多后端框架不会把它当作 JSON 解析。

const userForm = document.getElementById('userForm');

userForm.addEventListener('submit', async function (event) {
  event.preventDefault();

  const jsonString = serializeFormToJson(userForm, ['hobby']);

  const response = await fetch('/api/profile', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: jsonString
  });

  if (!response.ok) {
    throw new Error(`提交失败,状态码:${response.status}`);
  }

  const result = await response.json();
  console.log(result);
});
验证序列化结果的小技巧:在调试阶段,可以在浏览器开发者工具的“网络(Network)”面板中找到对应的请求,查看“载荷(Payload)”标签页。如果请求体里显示的是格式化后的 JSON 键值对,且请求头中包含 Content-Type: application/json,就说明序列化与发送格式已经正确。

五、文件上传与嵌套字段的边界说明

有两类表单场景不适合直接套用上面的 JSON 序列化逻辑,提前识别可以避免走弯路:

1. 包含文件选择器(input[type="file"])的表单

FormData 可以直接采集用户选中的文件对象(File 实例),但如果把包含 File 实例的对象交给 JSON.stringify(),File 对象没有可枚举的自有数据属性,序列化结果会变成空对象 {},文件内容完全丢失。

当表单需要上传文件时,推荐直接把 new FormData(form) 传给 fetch 的 body(且不要手动设置 Content-Type,让浏览器自动带上边界标识),而不是转成 JSON。只有在接口明确要求把小文件编码为 Base64 放进 JSON 时,才需要另外用 FileReader 异步读取文件内容。

2. 带点号或方括号的嵌套字段名

无论是 Object.fromEntries() 还是简单的键值对遍历,都会把 name="user.address" 或 name="items[0]" 当作字面量键名,生成扁平对象 {"user.address": "..."},而不会自动拆分成 {"user": {"address": "..."}}。如果后端需要嵌套 JSON 对象,可以在序列化前按分隔符做一次路径拆分赋值,或者直接使用支持路径映射的表单库来处理。

六、方法选择与常见排查清单

实际开发中没有必要一上来就手写复杂的遍历逻辑,可以根据表单控件的复杂度按需选择最简单、够用的方案:

JavaScript将表单数据序列化为JSON格式的方法
图 4:不同表单需求下将表单序列化为 JSON 的选型指南与边界提示

如果在实际使用中发现序列化后的 JSON 少了字段或者后端解析异常,可以对照下面几项快速排查:

常见现象 主要原因 解决方式
某个输入框的值完全没有出现在 JSON 中 控件缺少 name 属性,或设置了 disabled 补上 name 属性;需要禁止修改时改用 readonly
多选复选框勾选了多项,JSON 里只剩最后一项 直接使用了 Object.fromEntries() 导致同名键覆盖 改用遍历聚合函数或 formData.getAll(key)
文件字段变成了 {} File 对象被传入了 JSON.stringify() 改用 FormData 直接作为 fetch 的请求体上传
后端拿不到 JSON 字段或报解析错误 忘记设置 Content-Type: application/json 请求头,或未将对象转为字符串直接传给了 body 确认 headers 包含 'Content-Type': 'application/json' 且 body 为 JSON.stringify() 的返回值

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章