来谈谈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() 有了更好的理解。

相关推荐
阿珊和她的猫2 小时前
v-scale-scree: 根据屏幕尺寸缩放内容
开发语言·前端·javascript
PAK向日葵4 小时前
【算法导论】PDD 0817笔试题题解
算法·面试
加班是不可能的,除非双倍日工资6 小时前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi7 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip7 小时前
vite和webpack打包结构控制
前端·javascript
excel8 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国8 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼8 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy8 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
ZXT8 小时前
promise & async await总结
前端