JSON.stringify() 完整教程:对象到字符串的转换艺术
从一次接口调试失败说起
去年帮同事调试一个接口问题,他传数据一直报400错误。我看他的代码,直接 body: JSON.stringify(data) 没问题啊。后来打印出来才发现,他数据里有 undefined 字段,被 stringify 自动过滤掉了,导致接口要求的必传字段缺失。
这个经历让我深刻意识到,JSON.stringify() 不只是简单地把对象转成字符串,它背后有一套完整的处理规则。不理解这些规则,线上出问题都找不到原因。
方法签名深度解析
JSON.stringify(value[, replacer[, space]])
value 参数:要转换的值
这个参数可以是任何JavaScript值,但最终输出的结果会根据值的类型不同而有差异:
// 基础类型转换
JSON.stringify("代码号学习编程"); // '"代码号学习编程"'
JSON.stringify(2026); // "2026"
JSON.stringify(true); // "true"
JSON.stringify(null); // "null"
// 对象和数组
JSON.stringify({name: "张三", age: 25}); // '{"name":"张三","age":25}'
JSON.stringify([1, 2, 3]); // '[1,2,3]'
// 特殊值处理
JSON.stringify(undefined); // undefined (注意不是字符串)
JSON.stringify(function(){}); // undefined
JSON.stringify(Symbol()); // undefined
JSON.stringify(NaN); // "null"
JSON.stringify(Infinity); // "null"
replacer 参数:精细控制转换过程
这个参数是我在项目中用得最多的功能。它能让我们精确控制最终输出的JSON字符串包含哪些内容。
数组形式的replacer:属性白名单
const user = {
id: 10086,
username: "chengxuyuan",
password: "12345678", // 敏感信息,不想输出
email: "alan@ebingou.cn",
phone: "13800138000", // 敏感信息
role: "admin",
createdAt: "2026-03-15"
};
// 只保留非敏感字段
const safeUser = JSON.stringify(user, ["id", "username", "email", "role"]);
console.log(safeUser);
// 输出: {"id":10086,"username":"chengxuyuan","email":"alan@ebingou.cn","role":"admin"}
// password 和 phone 被自动过滤掉了
函数形式的replacer:自定义转换逻辑
const product = {
name: "JavaScript高级程序设计",
price: 129.99,
discount: 0.3,
stock: 500,
description: "这是一本很好的编程书籍,内容涵盖ES6+新特性",
tags: ["编程", "JavaScript", "前端"]
};
const jsonStr = JSON.stringify(product, (key, value) => {
// 价格保留整数
if (key === "price") {
return Math.round(value);
}
// 折扣信息不输出(商业机密)
if (key === "discount") {
return undefined; // 返回undefined会删除这个属性
}
// 描述信息截断
if (key === "description" && value.length > 20) {
return value.substring(0, 20) + "...";
}
// 标签转为大写
if (key === "tags" && Array.isArray(value)) {
return value.map(tag => tag.toUpperCase());
}
return value;
});
console.log(jsonStr);
// 输出: {"name":"JavaScript高级程序设计","price":130,"stock":500,"description":"这是一本很好的编程...","tags":["编程","JASCRIPT","前端"]}
space 参数:让JSON更可读
这个参数纯粹是为了人类阅读方便,对数据传输没有影响,但调试时非常有用:
const config = {
app: "代码号学习编程",
version: "2.3.0",
features: [
"在线编辑器",
"实时预览",
"代码保存",
"主题切换"
],
database: {
host: "localhost",
port: 3306,
username: "root",
password: "******"
}
};
// 不格式化 - 所有内容挤在一起
const compact = JSON.stringify(config);
console.log(compact);
// {"app":"代码号学习编程","version":"2.3.0","features":["在线编辑器","实时预览","代码保存","主题切换"],"database":{"host":"localhost","port":3306,"username":"root","password":"******"}}
// 缩进2空格 - 清晰可读
const pretty = JSON.stringify(config, null, 2);
console.log(pretty);
// {
// "app": "代码号学习编程",
// "version": "2.3.0",
// "features": [
// "在线编辑器",
// "实时预览",
// "代码保存",
// "主题切换"
// ],
// "database": {
// "host": "localhost",
// "port": 3306,
// "username": "root",
// "password": "******"
// }
// }
// 也可以指定缩进字符串
const custom = JSON.stringify(config, null, "---");
console.log(custom);
// 会用"---"作为缩进
项目开发中的核心应用场景
场景1:API请求数据格式化
async function createPost(postData) {
// 构建请求数据
const requestData = {
title: postData.title,
content: postData.content,
authorId: currentUser.id,
tags: postData.tags || [],
status: "draft",
createdAt: new Date().toISOString()
};
try {
const response = await fetch("/api/posts", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(requestData) // 必须转换
});
return await response.json();
} catch (error) {
console.error("发布失败:", error);
}
}
// 使用
createPost({
title: "JSON.stringify 使用技巧",
content: "在开发中我们经常需要...",
tags: ["JavaScript", "JSON"]
});
场景2:localStorage 存储复杂数据
// 保存用户设置
function saveSettings(settings) {
// 验证settings格式
if (!settings || typeof settings !== "object") {
throw new Error("设置必须是对象");
}
// 转成字符串存储
localStorage.setItem("user_settings", JSON.stringify(settings));
console.log("设置已保存");
}
// 读取设置
function loadSettings() {
const saved = localStorage.getItem("user_settings");
if (!saved) {
// 返回默认配置
return {
theme: "light",
fontSize: 14,
autosave: true,
language: "zh-CN"
};
}
try {
return JSON.parse(saved);
} catch (e) {
console.warn("设置损坏,使用默认值");
return {
theme: "light",
fontSize: 14,
autosave: true,
language: "zh-CN"
};
}
}
// 使用示例
saveSettings({
theme: "dark",
fontSize: 16,
autosave: false,
language: "en-US"
});
const settings = loadSettings();
console.log(`当前主题: ${settings.theme}`); // 当前主题: dark
场景3:日志记录和调试
function logAction(action, data) {
const logEntry = {
timestamp: new Date().toISOString(),
userId: getCurrentUserId(),
action: action,
data: data,
url: window.location.href,
userAgent: navigator.userAgent
};
// 格式化输出便于阅读
console.log("[ACTION LOG]", JSON.stringify(logEntry, null, 2));
// 发送到日志服务器(压缩格式节省带宽)
sendToLogServer(JSON.stringify(logEntry));
}
// 使用
logAction("button_click", { buttonId: "submit", page: "checkout" });
必须掌握的注意事项和
1:undefined、函数、Symbol会被忽略
const obj = {
name: "测试对象",
method: function() { console.log("hello"); },
undef: undefined,
sym: Symbol("test"),
prop: "这个会保留"
};
console.log(JSON.stringify(obj));
// 输出: {"name":"测试对象","prop":"这个会保留"}
// method, undef, sym 都消失了
2:循环引用会报错
const obj1 = { name: "对象1" };
const obj2 = { name: "对象2", ref: obj1 };
obj1.ref = obj2; // 循环引用
// JSON.stringify(obj1); // 报错: Corting circular structure to JSON
// 解决方案:检测循环引用
function safeStringify(obj) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return "[循环引用]";
}
seen.add(value);
}
return value;
});
}
console.log(safeStringify(obj1));
// 输出: {"name":"对象1","ref":{"name":"对象2","ref":"[循环引用]"}}
3:Date对象会变成字符串
const event = {
title: "技术分享会",
date: new Date("2026-03-15"),
attendees: ["张三", "李四"]
};
const json = JSON.stringify(event);
console.log(json);
// 输出: {"title":"技术分享会","date":"2026-03-15T00:00:00.000Z","attendees":["张三","李四"]}
// date 变成了 ISO 字符串,不再是 Date 对象
4:NaN、Infinity 变成 null
const stats = {
average: NaN,
max: Infinity,
min: -Infinity,
count: 100
};
console.log(JSON.stringify(stats));
// 输出: {"average":null,"max":null,"min":null,"count":100}
实用技巧和实践
技巧1:自定义toJSON方法
class User {
constructor(id, name, email, password) {
this.id = id;
this.name = name;
this.email = email;
this.password = password;
}
// 自定义JSON序列化行为
toJSON() {
return {
id: this.id,
name: this.name,
email: this.email,
// 故意不包含password
type: "user"
};
}
}
const user = new User(1, "张三", "zhangsan@example.com", "123456");
console.log(JSON.stringify(user));
// 输出: {"id":1,"name":"张三","email":"zhangsan@example.com","type":"user"}
// password 被成功排除
技巧2:格式化输出到文件
function exportConfig(config, filename = "config.json") {
// 创建格式化的JSON字符串
const jsonStr = JSON.stringify(config, null, 2);
// 创建下载链接
const blob = new Blob([jsonStr], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
// 使用
exportConfig({
siteName: "代码号学习编程",
version: "2.0",
features: ["教程", "实战", "问答"]
}, "site-config.json");
技巧3:比较对象差异
function objectDiff(obj1, obj2) {
const str1 = JSON.stringify(obj1, Object.keys(obj1).sort());
const str2 = JSON.stringify(obj2, Object.keys(obj2).sort());
if (str1 === str2) {
console.log("对象相同");
return true;
}
console.log("对象有差异");
console.log("对象1:", str1);
console.log("对象2:", str2);
return false;
}
const a = { name: "测试", count: 10, active: true };
const b = { name: "测试", count: 10, active: false };
objectDiff(a, b); // 输出差异
课程知识要点
-
JSON.stringify() 的核心功能:将JavaScript值转换为JSON格式的字符串
-
value参数处理规则:基础类型直接转换,特殊值(undefined/函数/Symbol)会被忽略或转为null
-
replacer数组用法:指定属性白名单,只输出数组中列出的属性
-
replacer函数用法:自定义每个键值对的转换逻辑,返回undefined可删除属性
-
space参数作用:美化输出格式,便于调试和日志记录,支持数字(缩进空格数)或字符串
-
toJSON方法:对象可以定义toJSON方法来控制序列化行为
-
循环引用问题:对象相互引用会导致错误,需要检测处理
-
Date对象处理:自动转为ISO格式字符串,不是所有场景都适用
-
数组序列化:保持数组结构,但元素同样遵循转换规则
-
性能考量:大对象频繁序列化影响性能,建议缓存结果
-
错误处理:始终用try-catch包裹,或实现safeStringify函数
为什么不能直接用toString
有人问我:为什么不直接用 obj.toString()?因为 toString() 对对象返回的是 "[object Object]",丢失了数据结构。手动拼接字符串更不可取,遇到嵌套对象和数组时复杂度过高。
JSON.stringify() 是专门为数据交换设计的标准方案,它保证了输出的字符串能被任何语言的JSON解析器正确解析,这是数据传输的基础。
掌握了 JSON.stringify(),你就能在JavaScript对象和可传输字符串之间自由转换。无论是调用后端API,还是保存用户配置到本地,都离不开它。