Skip to Content
JavaScriptJSON来回转换的坑

前言

项目中遇到一个 bug,一个组件为了保留一份 JSON 对象,使用 JSON.stringify 将其转换成字符串,这样做当然是为了避免对象是引用类型造成数据源的污染。

但发现后面使用 JSON.parse 方法之后,发现数据有所变化。

代码简化:

let obj = { name: "Gopal", age: Infinity, }; let originObj = JSON.stringify(obj); console.log(originObj); // {"name":"Gopal","age":null}

可以看到,Infinity 变成了 null,从而导致了后面的 bug。其实项目中自己踩 JSON.stringify 的坑已经很多了,借此机会好好整理一下,也给大家一个参考。

先说下这个问题的解决方法:

解决方法 1:

简单粗暴,重新给 age 属性赋值

解决方法 2:

function censor(key, value) { if (value === Infinity) { return "Infinity"; } return value; } var b = JSON.stringify(a, censor); var c = JSON.parse(b, function (key, value) { return value === "Infinity" ? Infinity : value; });

JSON.stringify 基础语法

JSON.stringify(value[, replacer [, space]])

JSON.stringify 强大的第二个参数 replacer

这个参数是可选的,可以是一个函数,也可以是一个数组

当是一个函数的时候,则在序列化的过程中,被序列化的每个属性都会经过该函数的转换和处理,看如下代码:

let replacerFun = function (key, value) { console.log(key, value); if (key === "name") { return undefined; } return value; }; let myIntro = { name: "Gopal", age: 25, like: "FE", }; console.log(JSON.stringify(myIntro, replacerFun)); // {"age":25,"like":"FE"}

这里其实就是一个筛选的作用,利用的是 JSON.stringify 中对象属性值为 undefined 就会在序列化中被忽略的特性(后面我们会提到)。

值得注意的是,在一开始 replacer 函数会被传入一个空字符串作为 key 值,代表着要被 stringify 的这个对象。

上面 console.log(key, value) 输出的值如下:

{ name: 'Gopal', age: 25, like: 'FE' } name Gopal age 25 like FE {"age":25,"like":"FE"}

可以看出,通过第二个参数,我们可以更加灵活的去操作和修改被序列化目标的值。

当第二个参数为数组的时候,只有包含在这个数组中的属性名才会被序列化:

JSON.stringify(myIntro, ["name"]); // {"name":"Gopal"}

中看不中用的第三个参数 指定缩进用的空白字符串,更多时候就是指定一个数字,代表几个空格:

let myIntro = { name: "Gopal", age: 25, like: "FE", }; console.log(JSON.stringify(myIntro)); console.log(JSON.stringify(myIntro, null, 2)); // {"name":"Gopal","age":25,"like":"FE"} // { // "name": "Gopal", // "age": 25, // "like": "FE" // }

JSON.stringify 使用场景

判断对象/数组值是否相等

let a = [1, 2, 3], b = [1, 2, 3]; JSON.stringify(a) === JSON.stringify(b); // true

localStorage/sessionStorage 存储对象

// 存 function setLocalStorage(key, val) { window.localStorage.setItem(key, JSON.stringify(val)); } // 取 function getLocalStorage(key) { let val = JSON.parse(window.localStorage.getItem(key)); return val; }

实现对象深拷贝

let myIntro = { name: "Gopal", age: 25, like: "FE", }; function deepClone() { return JSON.parse(JSON.stringify(myIntro)); } let copyMe = deepClone(myIntro); copyMe.like = "Fitness"; console.log(myIntro, copyMe); // { name: 'Gopal', age: 25, like: 'FE' } { name: 'Gopal', age: 25, like: 'Fitness' }

路由(浏览器地址)传参

因为浏览器传参只能通过字符串进行,所以也是需要用到 JSON.stringify。

JSON.stringify 使用注意事项

转换属性值中有 toJSON 方法,慎用

转换值中如果有 toJSON 方法,该方法返回的值将会是最后的序列化结果。

// toJSON let toJsonMyIntro = { name: "Gopal", age: 25, like: "FE", toJSON: function () { return "前端杂货铺"; }, }; console.log(JSON.stringify(toJsonMyIntro)); // "前端杂货铺"

被转换值中有 undefined、任意的函数以及 symbol 值,慎用

分为两种情况:

一种是数组对象,undefined、任意的函数以及 symbol 值会被转换成 null。

JSON.stringify([undefined, Object, Symbol("")]); // '[null,null,null]'

一种是非数组对象,在序列化的过程中会被忽略。

JSON.stringify({ x: undefined, y: Object, z: Symbol("") }); // '{}'

对于这种情况,我们可以使用 JSON.stringify 的第二个参数,使其达到符合我们的预期

const testObj = { x: undefined, y: Object, z: Symbol("test") }; const resut = JSON.stringify(testObj, function (key, value) { if (value === undefined) { return "undefined"; } else if (typeof value === "symbol" || typeof value === "function") { return value.toString(); } return value; }); console.log(resut); // {"x":"undefined","y":"function Object() { [native code] }","z":"Symbol(test)"}

包含循环引用的对象,慎用

let objA = { name: "Gopal", }; let objB = { age: 25, }; objA.age = objB; objB.name = objA; JSON.stringify(objA);

会报以下错误:

Uncaught TypeError: Converting circular structure to JSON --> starting at object with constructor 'Object' | property 'age' -> object with constructor 'Object' --### property 'name' closes the circle at JSON.stringify (<anonymous>) at <anonymous>:1:6

以 symbol 为属性键的属性,慎用

所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。

JSON.stringify({ [Symbol.for("foo")]: "foo" }, [Symbol.for("foo")]); // '{}' JSON.stringify({ [Symbol.for("foo")]: "foo" }, function (k, v) { if (typeof k === "symbol") { return "a symbol"; } }); // undefined

值为 NaN 和 Infinity,慎用

数组的值,或者非数组对象属性值为 NaN 和 Infinity 的,会被转换成 null。

let me = { name: "Gopal", age: Infinity, money: NaN, }; let originObj = JSON.stringify(me); console.log(originObj); // {"name":"Gopal","age":null,"money":null} JSON.stringify([NaN, Infinity]); // [null,null]

具有不可枚举的属性值时,慎用

不可枚举的属性默认会被忽略:

let person = Object.create(null, { name: { value: "Gopal", enumerable: false }, age: { value: "25", enumerable: true }, }); console.log(JSON.stringify(person)); // {"age":"25"}

总结

JSON.stringify 在实际应用中确实很方便的解决了我们很多问题,比如简单的深拷贝等。

但是我们在使用时候,也需要知道它有哪些不足之处,在目标值可能是一些特殊值的情况下,可能序列化后的结果会不符合我们的预期,这个时候就需要慎用。

来源

https://mp.weixin.qq.com/s/WayaFybS8GLyOLk9Jzi6pg

Last updated on