JSON往返并非无损复制:undefined在对象和数组中的不同结果

举报
yd_214130845 发表于 2026/10/23 17:34:40 2026/10/23
【摘要】 八组JavaScript实测:undefined在对象中被省略、在数组中变成null,非有限数和日期也会改变含义。附请求体边界校验代码,避免把JSON往返当作无损复制。

先看一个会悄悄丢字段的例子

把 JavaScript 对象存到本地,或拼装 JSON 请求体时,很容易顺手写成 JSON.parse(JSON.stringify(value))。这条路径能产生一个新对象,却不保证保留原来的数据含义。下面是本次在 JavaScript 运行环境中逐项运行的最小实验,未使用第三方库。

1. 同一个 undefined,位置不同,结果就不同

console.log(JSON.stringify({ name: 'demo', optional: undefined }));
// {"name":"demo"}
console.log(JSON.stringify([1, undefined, 3]));
// [1,null,3]
console.log(JSON.stringify(undefined));
// undefined(结果不是字符串)

对象属性 optional 被省略;数组中的位置仍然保留,但是值变成 null;顶层 undefined 则没有产生 JSON 文本。不能把这三个结果统一理解为“转成空值”。如果继续把第三个结果传给 JSON.parse,会抛出异常。

在更新接口里,属性不存在、显式 null 和空字符串,可能代表“保持原值”“清空字段”和“设置为空文本”三种不同意图。服务端如何解释需要由接口契约规定,前端不能依赖序列化的副作用决定业务语义。

2. 能成功序列化,却改变含义的输入

console.log(JSON.stringify({ n: NaN, p: Infinity, m: -Infinity }));
// {"n":null,"p":null,"m":null}
console.log(JSON.stringify(new Date('2026-10-23T00:00:00.000Z')));
// "2026-10-23T00:00:00.000Z"
console.log(JSON.stringify({ a: '', b: null, c: false, d: 0 }));
// {"a":"","b":null,"c":false,"d":0}

非有限数不会以原来的数值形态保存;Date 的结果是字符串,解析回来也不会自动恢复 Date 类型。相反,普通空字符串、null、false 和 0 能保留。过滤无效字段时,不宜直接用 if (value):它会把合法的 false 和 0 一起排除。

3. 两类会直接失败的输入

try {
  JSON.stringify({ id: 1n });
} catch (e) {
  console.log(e.name); // TypeError
}
const cyclic = {};
cyclic.self = cyclic;
try {
  JSON.stringify(cyclic);
} catch (e) {
  console.log(e.name); // TypeError
}

这里使用原生默认行为,没有添加 BigInt 的自定义序列化逻辑。BigInt 和循环引用两组都实际触发 TypeError。具体报错文本可能随运行环境变化,判断时可以先检查异常类型,避免依赖整段英文提示。

4. 发送前,在边界处明确规则

以可选备注和重试次数为例,先校验,再创建专门用于传输的对象:

function toPayload(input) {
  if (!Number.isInteger(input.retries) || input.retries < 0) {
    throw new TypeError('retries必须是非负整数');
  }
  const payload = { retries: input.retries };
  if (input.note !== undefined) {
    if (input.note !== null && typeof input.note !== 'string') {
      throw new TypeError('note必须是字符串、null或不提供');
    }
    payload.note = input.note;
  }
  return JSON.stringify(payload);
}
console.log(toPayload({ retries: 0 }));
// {"retries":0}
console.log(toPayload({ retries: 0, note: null }));
// {"retries":0,"note":null}

示例有意允许 note 为 null,但这只是一份演示契约,实际项目应跟后端约定。对于必须精确保留的大整数,可以在双方约定后使用十进制字符串;日期也要约定格式和恢复方式。

实验后的检查顺序

遇到“本地对象有值,后端却没收到”的问题,先看最终请求体,再对照原对象检查 undefined、非有限数、日期及自定义序列化行为。需要复制数据时,应先确定需要保留的类型和引用关系,再选择复制方案。JSON 适合传递约定好的 JSON 数据,不应默认承担任意 JavaScript 对象的无损复制。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。