← JavaScript JSON.parse() JavaScript表单验证 →

JavaScript JSON.stringify()

原创 2026-03-15 JavaScript 已有人查阅

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,还是保存用户配置到本地,都离不开它。

← JavaScript JSON.parse() JavaScript表单验证 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号