JSON往返并非无损复制:undefined在对象和数组中的不同结果
先看一个会悄悄丢字段的例子
把 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 对象的无损复制。
- 点赞
- 收藏
- 关注作者
评论(0)