来谈谈JSON.stringify

JSON 在很多地方被用于不同服务/系统之间的数据交换。如今,所有主流编程语言都内置了对 JSON 数据解析和序列化的库支持。在这篇文章中,我们来谈谈 JavaScript 中的 JSON.stringify()。

首先来看一个对象转换处理的小例子。有一个需求是将下面这个对象:

javascript 复制代码
const todayILearn = {
  _id: 1,
  content: '今天学习 JSON.stringify(),我很开心!',
  created_at: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中国标准时间)',
  updated_at: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中国标准时间)'
}

转换为下面这个对象:

javascript 复制代码
const todayILearn = {
  id: 1,
  content: '今天学习 JSON.stringify(),我很开心!',
  createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中国标准时间)',
  updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中国标准时间)'
}

这里的变化是:

  • 将属性名"_id"改为"id"。
  • 将属性名"created_at"和"updated_at"改为"createdAt"和"updatedAt"。
  • 值保持不变。

有很多方法可以实现上述需求。

  1. 遍历所有属性
javascript 复制代码
const todayILearnTemp = {};
for (const [key, value] of Object.entries(todayILearn)) {
  if (key === "_id") todayILearnTemp["id"] = value;
  else if (key === "created_at") todayILearnTemp["createdAt"] = value;
  else if (key === "updatedAt") todayILearnTemp["updatedAt"] = value;
  else todayILearnTemp[key] = value;
}
console.log(todayILearnTemp);
// 输出:
// { id: 1,
//  content: '今天学习 JSON.stringify(),我很开心!',
//  createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中国标准时间)',
//  updated_at: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中国标准时间)' 
// }

这种方法创建了一个新的临时变量,遍历所有属性并引入了很多 if - else 语句,一点也不优雅且不可扩展。 2. 删除并创建属性 可以添加三个新属性并删除三个不需要的属性。

javascript 复制代码
todayILearn.id = todayILearn._id;
todayILearn.createdAt = todayILearn.created_at;
todayILearn.updatedAt = todayILearn.updated_at;
delete todayILearn._id;
delete todayILearn.created_at;
delete todayILearn.updated_at;
console.log(todayILearn);
// 输出:
// { 
//  content: '今天学习 JSON.stringify(),我很开心!',
//  id: 1,
//  createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中国标准时间)',
//  updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中国标准时间)' 
// }

它与上面的方法有相同的缺点,并且属性的顺序也改变了。 3. 使用 JSON.stringify() JSON.stringify() 可以用来将不需要的属性名替换为想要的属性名。

javascript 复制代码
const mapObj = {
  _id: "id",
  created_at: "createdAt",
  updated_at: "updatedAt"
};
JSON.parse(
  JSON.stringify(todayILearn).replace(
    /_id|created_at|updated_at/gi,
    matched => mapObj[matched])
    )
// { 
// id: 1,
//  content: '今天学习 JSON.stringify(),我很开心!',
//  createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中国标准时间)',
//  updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中国标准时间)' 
// }

现在看起来优雅多了。

现在让我们来了解一下 JSON.stringify() 的主要规则,以便在需要时能够正确有效地使用它。

规则 1

如果undefined、函数和符号出现在对象属性值、数组中或作为独立值,它们的输出值会有所不同。 猜猜下面代码片段的输出会是什么。

javascript 复制代码
const data = {
  a: "aaa",
  b: undefined,
  c: Symbol("dd"),
  fn: function() {
    return true;
  }
};
JSON.stringify(data); // 输出:?

// "{"a":"aaa"}"

undefined、函数和符号作为对象属性值出现时,在序列化时会被忽略。

那如果出现在数组中呢?

javascript 复制代码
JSON.stringify(["aaa", undefined, function aa() {
    return true
  }, Symbol('dd')])  // 输出:?

// "["aaa",null,null,null]"

undefined、函数和符号出现在数组中时,它们会被转换为null

如果它们独立出现呢?

javascript 复制代码
JSON.stringify(function a (){console.log('a')})
// undefined
JSON.stringify( undefined)
// undefined
JSON.stringify(Symbol('dd'))
// undefined

undefined、函数和符号独立出现时,它们将是undefined

规则 2

如果不是数组对象,序列化后的属性可能不会保持原始顺序。

javascript 复制代码
const data = {
  a: "aaa",
  b: undefined,
  c: Symbol("dd"),
  fn: function() {
    return true;
  },
  d: "ddd"
};
JSON.stringify(data); // 输出:?
// "{"a":"aaa","d":"ddd"}"

JSON.stringify(["aaa", undefined, function aa() {
    return true
  }, Symbol('dd'),"eee"])  // 输出:?

// "["aaa",null,null,null,"eee"]"

正如规则 1 所提到的,一些属性如果是undefined、函数或符号之一,在序列化时会被忽略。因此顺序可能无法保持。

规则 3

如果一个对象实现了toJSON()函数,JSON.stringify() 的输出值将是toJSON()函数的返回值。

javascript 复制代码
JSON.stringify({
    say: "hello JSON.stringify",
    toJSON: function() {
      return "today i learn";
    }
  })
// "today i learn"

规则 4

NaNInfinitynull将被序列化为null

javascript 复制代码
JSON.stringify(NaN)
// "null"
JSON.stringify(null)
// "null"
JSON.stringify(Infinity)
// "null"

规则 5

布尔值、数字或字符串对象将被转换为它们相应的原始值。

javascript 复制代码
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
// "[1,"false",false]"

规则 6

只有可枚举的属性才能被序列化,包括Map/Set/WeakMap/WeakSet等。

javascript 复制代码
JSON.stringify( 
    Object.create(
        null, 
        { 
            x: { value: 'json', enumerable: false }, 
            y: { value: 'stringify', enumerable: true } 
        }
    )
);
// "{"y","stringify"}"

规则 7

很多人可能知道,深度克隆一个对象最简单但最粗暴的方法是使用JSON.parse(JSON.stringify(obj))。但如果要转换的对象包含循环引用,它会有一些意想不到的行为。

javascript 复制代码
const obj = {
  name: "loopObj"
};
const loopObj = {
  obj
};
obj.loopObj = loopObj;
function deepClone(obj) {
  return JSON.parse(JSON.stringify(obj));
}
deepClone(obj)
/**
 VM44:9 Uncaught TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    |     property 'loopObj' -> object with constructor 'Object'
    --- property 'obj' closes the circle
    at JSON.stringify (<anonymous>)
    at deepClone (<anonymous>:9:26)
    at <anonymous>:11:13
 */

如果对象中有循环引用,在序列化时会抛出异常。

规则 8

即使定义了替换器(replacer)来处理符号,任何以符号作为键的属性都将被忽略。

javascript 复制代码
JSON.stringify({ [Symbol.for("json")]: "stringify" }, function(k, v) {
    if (typeof k === "symbol") {
      return v;
    }
  })

// undefined

替换器可以是一个函数或一个数组。如果是一个函数,它可以用来覆盖上面的很多规则。

javascript 复制代码
const data = {
  a: "aaa",
  b: undefined,
  c: Symbol("dd"),
  fn: function() {
    return true;
  }
};
// 不使用替换器时
JSON.stringify(data); 

// "{"a":"aaa"}"

// 使用替换器时
JSON.stringify(data, (key, value) => {
  switch (true) {
    case typeof value === "undefined":
      return " undefined";
    case typeof value === "symbol":
      return value.toString();
    case typeof value === "function":
      return value.toString();
    default:
      break;
  }
  return value;
})
// "{"a":"aaa","b":" undefined","c":"Symbol(dd)","fn":"function() {\n    return true;\n  }"}"

如果替换器是一个数组,它包含了在序列化时应该保留的属性列表,其余的将被删除。

javascript 复制代码
const jsonObj = {
  name: "JSON.stringify",
  params: "obj,replacer,space"
};

JSON.stringify(jsonObj, ["params"]);
// "{"params":"obj,replacer,space"}"

希望你现在对 JSON.stringify() 有了更好的理解。

相关推荐
文刀竹肃4 分钟前
DVWA -SQL Injection-通关教程-完结
前端·数据库·sql·安全·网络安全·oracle
LYFlied9 分钟前
【每日算法】LeetCode 84. 柱状图中最大的矩形
前端·算法·leetcode·面试·职场和发展
Bigger11 分钟前
Tauri(21)——窗口缩放后的”失焦惊魂”,游戏控制权丢失了
前端·macos·app
Bigger30 分钟前
Tauri (20)——为什么 NSPanel 窗口不能用官方 API 全屏?
前端·macos·app
bug总结31 分钟前
前端开发中为什么要使用 URL().origin 提取接口根地址
开发语言·前端·javascript·vue.js·html
zwjapple1 小时前
全栈开发面试高频算法题
算法·面试·职场和发展
程序员爱钓鱼1 小时前
Node.js 编程实战:数据库连接池与性能优化
javascript·后端·node.js
程序员爱钓鱼1 小时前
Node.js 编程实战:Redis缓存与消息队列实践
后端·面试·node.js
Gomiko2 小时前
JavaScript DOM 原生部分(二):元素内容修改
开发语言·javascript·ecmascript
一招定胜负2 小时前
网络爬虫(第三部)
前端·javascript·爬虫