JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式,几乎贯穿了现代Web开发的每一个环节。无论是接口返回数据、本地存储还是配置文件,都离不开JSON的身影。JavaScript原生提供了两个核心方法来处理JSON:JSON.parse()负责把JSON字符串解析成JavaScript对象,JSON.stringify()负责把JavaScript对象序列化成JSON字符串。理解这两个方法的工作机制和边界情况,是用好JSON的关键。

一、JSON字符串解析:JSON.parse的正确用法
从服务器拿到接口数据后,第一步通常就是解析。JSON.parse()接收一个JSON格式的字符串,返回对应的JavaScript值。最基本的用法非常简单:
const jsonStr = '{"name":"张三","age":25,"skills":["JS","CSS"]}';
const obj = JSON.parse(jsonStr);
console.log(obj.name); // 张三
console.log(obj.skills[0]); // JS这里有一个非常容易踩的坑:JSON字符串的键和字符串值必须使用双引号,不能用单引号。下面这段代码会直接抛出异常:
// 错误示范:单引号不是合法的JSON格式
try {
const bad = JSON.parse("{'name':'张三'}");
} catch (e) {
console.log(e.name); // SyntaxError
}由于解析失败会抛出SyntaxError,在生产环境中建议始终用try...catch包裹解析逻辑,特别是在处理用户输入或第三方接口返回的数据时, malformed的JSON字符串如果处理不当,会导致后续代码全部中断执行。
JSON.parse()还支持第二个参数reviver(复活函数),可以在返回结果之前对每一个键值对做转换处理。这个功能常用于日期字符串还原成Date对象,或者对某些敏感字段做解密:
const str = '{"name":"李四","createTime":"2023-06-01T10:00:00Z"}';
const obj = JSON.parse(str, (key, value) => {
if (key === 'createTime') {
return new Date(value); // 把日期字符串还原为Date对象
}
return value;
});
console.log(obj.createTime.getFullYear()); // 2023需要注意的是,reviver函数是自底向上执行的,也就是说最内层的属性会先被处理,最后处理的键是空字符串,对应的value是整个解析后的对象。如果reviver返回undefined,该属性会从结果对象中被删除。
二、对象序列化:JSON.stringify的细节与技巧
序列化方向的坑比解析更多一些。JSON.stringify()把JavaScript值转换成JSON字符串,但它并不是"什么都收"。有几类值在序列化时会被自动处理甚至丢弃:
undefined、函数、Symbol作为对象属性值时,序列化结果中该属性直接消失undefined、函数、Symbol作为数组元素时,会被转换成null,以保证数组长度不变NaN和Infinity会被转换成nullDate对象会调用toJSON()方法,输出ISO格式的字符串
const data = {
name: '王五',
age: undefined,
sayHi: function() { console.log('hi'); },
birthday: new Date('1995-08-15'),
scores: [98, undefined, 76]
};
console.log(JSON.stringify(data));
// {"name":"王五","birthday":"1995-08-15T00:00:00.000Z","scores":[98,null,76]}第二个参数replacer非常有用,它可以是数组也可以是函数。传数组表示白名单,只保留指定的键;传函数则可以对每个值做定制化处理,比如隐藏密码字段:
const user = { name: '赵六', password: '123456', age: 30 };
// 白名单方式:只序列化name和age
const a = JSON.stringify(user, ['name', 'age']);
// 函数方式:过滤掉密码字段
const b = JSON.stringify(user, (key, value) => {
if (key === 'password') return undefined; // 返回undefined即删除该字段
return value;
});
console.log(a); // {"name":"赵六","age":30}
console.log(b); // {"name":"赵六","age":30}第三个参数space用于格式化输出,传数字表示缩进的空格数,也可以传字符串作为缩进符。这在生成配置文件或调试时很实用,但要注意格式化后的字符串体积会明显变大,网络传输时不要加这个参数。
三、循环引用与深拷贝:进阶场景处理
当对象存在循环引用时,JSON.stringify()会直接抛出TypeError。这种情况在有双向关联的数据结构中很常见,比如树的子节点持有父节点引用:
const node = { id: 1 };
node.self = node; // 循环引用
try {
JSON.stringify(node);
} catch (e) {
console.log(e.message); // Converting circular structure to JSON
}解决办法是利用replacer参数维护一个已访问对象的缓存,遇到重复访问就返回占位值:
function safeStringify(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;
});
}另一个经典应用是借助JSON.parse(JSON.stringify(obj))实现深拷贝。这个写法简单粗暴,但要清楚它的局限性:函数和undefined属性会丢失,Date会变成字符串,RegExp、Map、Set都会变成空对象{},无法处理循环引用。对于包含这些类型的复杂对象,还是应该使用结构化克隆structuredClone()或者递归克隆函数,或者引入lodash这类成熟的工具库。
在性能层面也要有意识:JSON的序列化和解析都是全量操作,数据量大时会成为性能瓶颈。高频场景下可以考虑只序列化需要的字段,或者在传输层换用更轻量的格式。另外,JSON.parse是同步阻塞的,解析超大JSON字符串(几MB以上)可能造成页面卡顿,必要可以把解析工作放到Web Worker中执行,避免阻塞主线程。
总结一下,JSON操作的核心就是parse和stringify这一对方法,配合replacer和reviver两个参数能覆盖绝大多数定制化需求。日常开发中记住三点:解析一定要做异常捕获,序列化前想清楚特殊值会变成什么,深拷贝要确认数据类型是否被完整支持。把这些细节吃透,处理JSON数据时就能做到心里有底。
JSON解析JSON.stringifyJSON.parse修改时间:2026-09-13 01:38:29