为什么理解 JSON 序列化是数据处理的基础
前后端数据传输、状态管理、本地存储——几乎所有前端数据处理场景都离不开 JSON。理解 JSON.stringify 和 JSON.parse 的完整行为,才能避免序列化相关的 bug。
这篇文章深入解析 JavaScript 的 JSON 序列化机制,包括常见陷阱和高级用法。
一、JSON.stringify
1.1 基本用法
const obj = { name: 'Alice', age: 30 };
const json = JSON.stringify(obj);
console.log(json); // '{"name":"Alice","age":30}'
// 反序列化
const parsed = JSON.parse(json);
console.log(parsed); // { name: 'Alice', age: 30 }
1.2 第二个参数:replacer
replacer 可以是函数或数组:
// 函数 replacer
const obj = { name: 'Alice', age: 30, password: 'secret' };
const filtered = JSON.stringify(obj, (key, value) => {
if (key === 'password') return undefined; // 排除密码
return value;
});
console.log(filtered); // '{"name":"Alice","age":30}'
// 数组 replacer — 只包含指定键
const partial = JSON.stringify(obj, ['name', 'age']);
console.log(partial); // '{"name":"Alice","age":30}'
1.3 第三个参数:space
const obj = { name: 'Alice', items: [1, 2, 3] };
// 数字 space:缩进
console.log(JSON.stringify(obj, null, 2));
/*
{
"name": "Alice",
"items": [
1,
2,
3
]
}
*/
// 字符串 space:键名前缀
console.log(JSON.stringify(obj, null, '\t'));
// 使用 tab 缩进
// 自定义前缀
console.log(JSON.stringify(obj, null, '>>'));
/*
>>
>>"name": "Alice",
>>"items": [
>>>>1,
>>>>2,
>>>>3
>>]
}
*/
二、toJSON 自定义序列化
2.1 toJSON 方法
对象可以实现 toJSON 方法自定义序列化:
const user = {
name: 'Alice',
age: 30,
password: 'secret',
toJSON() {
return {
name: this.name,
age: this.age
// 排除 password
};
}
};
console.log(JSON.stringify(user));
// '{"name":"Alice","age":30}'
2.2 常见应用:日期序列化
const event = {
title: 'Meeting',
date: new Date('2024-04-08T10:00:00Z'),
// Date 有 toJSON,返回 ISO 字符串
};
console.log(JSON.stringify(event));
// '{"title":"Meeting","date":"2024-04-08T10:00:00.000Z"}'
// 自定义日期格式
const event2 = {
title: 'Meeting',
date: {
toJSON: () => '2024-04-08'
}
};
三、JSON.parse
3.1 基本用法
const json = '{"name":"Alice","age":30}';
const obj = JSON.parse(json);
console.log(obj.name); // 'Alice'
3.2 reviver 参数
reviver 函数在解析时转换值:
const json = '{"createdAt":"2024-04-08T10:00:00Z","score":100}';
// 转换日期字符串为 Date 对象
const obj = JSON.parse(json, (key, value) => {
if (key === 'createdAt') {
return new Date(value);
}
return value;
});
console.log(obj.createdAt instanceof Date); // true
console.log(obj.score); // 100
3.3 错误处理
// 无效 JSON 会抛出 SyntaxError
try {
JSON.parse('{invalid}');
} catch (e) {
console.log(e instanceof SyntaxError); // true
console.log(e.message); // JSON.parse error
}
// 安全解析函数
function safeJsonParse(str, reviver) {
try {
return { success: true, data: JSON.parse(str, reviver) };
} catch (e) {
return { success: false, error: e.message };
}
}
const result = safeJsonParse('{invalid}');
console.log(result.success); // false
四、序列化边界情况
4.1 undefined、函数、Symbol
const obj = {
a: 1,
b: undefined,
c: function() {},
d: Symbol('test'),
e: ['x', undefined, 'y']
};
console.log(JSON.stringify(obj));
// '{"a":1,"e":["x",null,"y"]}'
// 规则:
// - undefined → 忽略(数组中变为 null)
// - 函数 → 忽略
// - Symbol → 忽略
4.2 NaN 和 Infinity
console.log(JSON.stringify({ value: NaN })); // '{"value":null}'
console.log(JSON.stringify({ value: Infinity })); // '{"value":null}'
console.log(JSON.stringify({ value: -Infinity })); // '{"value":null}'
4.3 循环引用
const obj = { name: 'Alice' };
obj.self = obj;
try {
JSON.stringify(obj);
} catch (e) {
console.log(e.message); // Converting circular reference to JSON
}
// 解决方案:检测循环引用
function stringifyCircular(obj) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[Circular]';
}
seen.add(value);
}
return value;
});
}
4.4 BigInt
// BigInt 无法序列化
try {
JSON.stringify({ value: 42n });
} catch (e) {
console.log(e.message); // Do not know how to serialize a BigInt
}
// 解决方案:转换为字符串
function replacer(key, value) {
if (typeof value === 'bigint') {
return value.toString();
}
return value;
}
五、structuredClone
5.1 原生深拷贝
ES2021 引入 structuredClone,提供原生深拷贝:
const original = {
name: 'Alice',
nested: { value: 1 },
date: new Date(),
map: new Map([['a', 1], ['b', 2]])
};
const clone = structuredClone(original);
// 修改克隆不影响原对象
clone.nested.value = 100;
console.log(original.nested.value); // 1
5.2 structuredClone 的优势
// 支持更多类型
const obj = {
date: new Date(),
map: new Map(),
set: new Set(),
arrayBuffer: new ArrayBuffer(8),
error: new Error('test')
};
const clone = structuredClone(obj);
// structuredClone vs JSON.stringify
// - JSON.stringify 无法处理函数、undefined、Symbol、BigInt
// - structuredClone 支持更多内置类型
// - 结构化克隆保留对象类型
5.3 structuredClone 的限制
// 无法克隆函数
try {
structuredClone({ fn: () => {} });
} catch (e) {
console.log(e); // DataCloneError
}
// 无法克隆 DOM 节点
try {
structuredClone(document.body);
} catch (e) {
console.log(e); // DataCloneError
}
// 无法克隆循环引用(但可以克隆某些循环引用结构)
const obj = { name: 'test' };
obj.self = obj;
// structuredClone(obj); // DataCloneError