Skip to content
概述
JSON.stringify 在处理 undefined 时,对象属性与数组元素的行为并不一致。这种差异源于 JSON 规范本身不定义 undefined,只有 null 表示空值。如果需要将数据中的所有 undefined 统一替换为 null 再进行序列化,可以编写一个递归转换函数。LeetCode 第 2775 题“将 undefined 转为 null”练习的就是这个转换。
基本概念
JSON 中没有 undefined 的概念。JSON.stringify 在面对 undefined 时会按照以下规则处理:
- 当对象的属性值为
undefined时,序列化结果会直接省略该属性。 - 当数组的元素为
undefined时,序列化结果中该位置会输出null,而不是跳过。
javascript
const obj = { a: undefined, b: 3 };
console.log(JSON.stringify(obj)); // {"b":3}
const arr = [undefined, 3];
console.log(JSON.stringify(arr)); // [null,3]如果数据中存在 undefined 却未提前转换,JSON.stringify 的这种差异可能导致属性丢失或语义偏差。事先将所有 undefined 替换为 null,可以让序列化行为变得可预期。
工作原理
递归遍历整个对象树,对每个值执行:
- 如果值为
undefined,直接返回null。 - 如果值为基本类型(包括
null),原样返回。 - 如果值为对象或数组,对每个属性或元素递归执行上述逻辑,最后返回原对象。
需要注意 typeof null === 'object',因此在判断引用类型之前必须优先排除 null。
基本用法
以下实现使用 LeetCode 提供的类型签名,并纠正了常见的笔误(typeof obj === null)。
typescript
function undefinedToNull(obj: any): any {
if (obj === undefined) {
return null;
}
if (obj === null || typeof obj !== 'object') {
return obj;
}
for (const key in obj) {
obj[key] = undefinedToNull(obj[key]);
}
return obj;
}调用示例:
javascript
console.log(undefinedToNull({ a: undefined, b: 3 }));
// { a: null, b: 3 }
console.log(undefinedToNull([undefined, undefined]));
// [null, null]
console.log(undefinedToNull({ x: { y: undefined, z: [1, undefined, 2] } }));
// { x: { y: null, z: [1, null, 2] } }经过转换后的对象再传入 JSON.stringify,就不会再出现属性丢失或数组元素行为不一致的问题。
注意点
typeof obj === null 是错误的写法
typeof 运算符永远返回字符串,typeof anything === null 的值永远为 false。在某些讨论区中出现的这种笔误不应进入实际代码。正确的写法是直接使用 obj === null。
判断顺序不能颠倒
undefinedToNull 在处理 undefined 和 null 之前,不应直接进入引用类型判断。如果先执行 typeof obj !== 'object',当 obj 为 null 时,由于 typeof null === 'object',该条件为 false,程序会错误地进入递归并尝试用 for...in 遍历 null。虽然不会抛出错误,但逻辑上不正确且会产生额外迭代。
正确的顺序是:
typescript
if (obj === undefined) return null;
if (obj === null || typeof obj !== 'object') return obj;for...in 与稀疏数组
for...in 会枚举对象的所有可枚举属性。对于数组,它只遍历已存在的索引,会跳过稀疏数组中的空位。
javascript
const arr = [0, , 2]; // 稀疏数组,索引 1 为空位
console.log(0 in arr); // false
console.log(undefinedToNull(arr));
// 结果仍是 [0, <1 empty>, 2],空位未被转换为 null如果要求所有空位也转换为 null,应当改用索引遍历或先转为稠密数组再处理。例如通过普通 for 循环按数组长度遍历:
typescript
function undefinedToNull(obj: any): any {
if (obj === undefined) return null;
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
obj[i] = undefinedToNull(obj[i]);
}
return obj;
}
for (const key of Object.keys(obj)) {
obj[key] = undefinedToNull(obj[key]);
}
return obj;
}for 循环按长度遍历,无论空位是否存在都会执行赋值操作,最终空位会显式变为 null(即 [0, null, 2])。对普通对象使用 Object.keys 也更可靠。
就地修改
上述实现会直接修改传入的原始对象。如果需要保留原始数据不变,应在遍历时创建新的对象或数组并返回:
typescript
function undefinedToNull(obj: any): any {
if (obj === undefined) return null;
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) {
return obj.map(item => undefinedToNull(item));
}
const result: Record<string, any> = {};
for (const key of Object.keys(obj)) {
result[key] = undefinedToNull(obj[key]);
}
return result;
}应用
在将 JavaScript 对象序列化为 JSON 发送到后端或存储之前,提前将 undefined 统一替换为 null,可以消除属性丢失的风险,同时保证数组语义一致。
某些严格类型检查的接口(例如 GraphQL 的变量输入)也不接受 undefined 作为值,统一转换为 null 有助于通过接口校验。
