一、假设我们有一个复杂的对象,我们希望将其转换为字符串,以通过网络发送,或者只是为了在日志中输出它。
1、当然,这样的字符串应该包含所有重要的属性。
2、我们可以像这样实现转换:
let user = {
name: "John",
age: 30,
toString() {
return `{name: "${this.name}", age: ${this.age}}`;
}
};
alert(user); // {name: "John", age: 30}
3、但在开发过程中,会新增一些属性,旧的属性会被重命名和删除。每次更新这种toString都会非常痛苦。我们可以尝试遍历其中的属性,但是如果对象很复杂,并且在属性中嵌套了对象呢?我们也需要对它们进行转换。
4、幸运的是,不需要编写代码来处理所有这些问题。这项任务已经解决了。
二、JSON(JavaScript Object Notation)是表示值和对象的通用格式。在RFC 4627标准中有对其的描述。最初它是为 JavaScript 而创建的,但许多其他编程语言也有用于处理它的库。因此,当客户端使用 JavaScript 而服务器端是使用 Ruby/PHP/Java 等语言编写的时,使用 JSON 可以很容易地进行数据交换。
1、JSON 是一种数据格式,具有自己的独立标准和大多数编程语言的库。
三、JavaScript 提供了如下方法:
- JSON.stringify将对象转换为 JSON。
- JSON.parse将 JSON 转换回对象。
四、JSON 支持 object,array,string,number,boolean 和null
JSON.stringify:序列化(serialize)成 JSON 的方法
一、【示例1】在这里我们JSON.stringify一个student对象:
let student = {
name: 'John',
age: 30,
isAdmin: false,
courses: ['html', 'css', 'js'],
wife: null
};
let json = JSON.stringify(student);
alert(typeof json); // we've got a string!
alert(json);
/* JSON 编码的对象:
{
"name": "John",
"age": 30,
"isAdmin": false,
"courses": ["html", "css", "js"],
"wife": null
}
*/
1、方法JSON.stringify(student)接收对象并将其转换为字符串。
二、得到的json字符串是一个被称为JSON 编码(JSON-encoded)或序列化(serialized)或字符串化(stringified)或编组化(marshalled)的对象。我们现在已经准备好通过有线发送它或将其放入普通数据存储。
三、JSON 编码的对象与对象字面量有几个重要的区别:
- 字符串使用双引号。JSON 中没有单引号或反引号。所以’John’被转换为”John”。
- 对象属性名称也是双引号的。这是强制性的。所以age:30被转换成”age”:30。
四、JSON 支持以下数据类型:
- Objects{ … }
- Arrays[ … ]
- Primitives(原始数据类型):
- strings,
- numbers,
- boolean valuestrue/false,
- null。
【示例1】
// 数字在 JSON 还是数字
alert( JSON.stringify(1) ) // 1
// 字符串在 JSON 中还是字符串,只是被双引号扩起来
alert( JSON.stringify('test') ) // "test"
alert( JSON.stringify(true) ); // true
alert( JSON.stringify([1, 2, 3]) ); // [1,2,3]
五、支持嵌套对象转换,并且可以自动对其进行转换。
【示例1】
let meetup = {
title: "Conference",
room: {
number: 23,
participants: ["john", "ann"]
}
};
alert( JSON.stringify(meetup) );
/* 整个解构都被字符串化了
{
"title":"Conference",
"room":{"number":23,"participants":["john","ann"]},
}
*/
参数
排除和转换:replacer
一、JSON.stringify的完整语法是:
let json = JSON.stringify(value[, replacer, space])
- value:要编码的值。
- replacer:要编码的属性数组或映射函数function(key, value)。
- space:用于格式化的空格数量
二、大部分情况,JSON.stringify仅与第一个参数一起使用。但是,如果我们需要微调替换过程,比如过滤掉循环引用,我们可以使用JSON.stringify的第二个参数。
三、如果我们传递一个属性数组给它,那么只有这些属性会被编码。
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup 引用了 room
};
room.occupiedBy = meetup; // room 引用了 meetup
alert( JSON.stringify(meetup, ['title', 'participants']) );
// {"title":"Conference","participants":[{},{}]}
1、这里我们可能过于严格了。属性列表应用于了整个对象结构。所以participants是空的,因为name不在列表中。
2、让我们包含除了会导致循环引用的room.occupiedBy之外的所有属性:
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup 引用了 room
};
room.occupiedBy = meetup; // room 引用了 meetup
alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) );
/*
{
"title":"Conference",
"participants":[{"name":"John"},{"name":"Alice"}],
"place":{"number":23}
}
*/
3、现在,除occupiedBy以外的所有内容都被序列化了。但是属性的列表太长了。
4、幸运的是,我们可以使用一个函数代替数组作为replacer。
(1)该函数会为每个(key,value)对调用并返回“已替换”的值,该值将替换原有的值。如果值被跳过了,则为undefined。
5、在我们的例子中,我们可以为occupiedBy以外的所有内容按原样返回value。为了occupiedBy,下面的代码返回undefined:
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup 引用了 room
};
room.occupiedBy = meetup; // room 引用了 meetup
alert( JSON.stringify(meetup, function replacer(key, value) {
alert(`${key}: ${value}`);
return (key == 'occupiedBy') ? undefined : value;
}));
/* key:value pairs that come to replacer:
: [object Object]
title: Conference
participants: [object Object],[object Object]
0: [object Object]
name: John
1: [object Object]
name: Alice
place: [object Object]
number: 23
*/
(1)请注意replacer函数会获取每个键/值对,包括嵌套对象和数组项。它被递归地应用。replacer中的this的值是包含当前属性的对象。
(2)第一个调用很特别。它是使用特殊的“包装对象”制作的:{“”: meetup}。换句话说,第一个(key, value)对的键是空的,并且该值是整个目标对象。这就是上面的示例中第一行是”:[object Object]”的原因。
(3)这个理念是为了给replacer提供尽可能多的功能:如果有必要,它有机会分析并替换/跳过整个对象。
格式化:space
一、JSON.stringify(value, replacer, spaces)的第三个参数是用于优化格式的空格数量。
二、以前,所有字符串化的对象都没有缩进和额外的空格。如果我们想通过网络发送一个对象,那就没什么问题。space参数专门用于调整出更美观的输出。
【示例1】这里的space = 2告诉 JavaScript 在多行中显示嵌套的对象,对象内部缩紧 2 个空格:
let user = {
name: "John",
age: 25,
roles: {
isAdmin: false,
isEditor: true
}
};
alert(JSON.stringify(user, null, 2));
/* 两个空格的缩进:
{
"name": "John",
"age": 25,
"roles": {
"isAdmin": false,
"isEditor": true
}
}
*/
/* 对于 JSON.stringify(user, null, 4) 的结果会有更多缩进:
{
"name": "John",
"age": 25,
"roles": {
"isAdmin": false,
"isEditor": true
}
}
*/
JSON.stringify使用场景
判断对象/数组值是否相等
let a = [1,2,3],
b = [1,2,3];
JSON.stringify(a) === JSON.stringify(b);// true
localStorage/sessionStorage存储对象
一、localStorage/sessionStorage只可以存储字符串,当我们想存储对象的时候,需要使用JSON.stringify转换成字符串,获取的时候再JSON.parse
// 存
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
POST请求中的JSON body
处理响应体中的JSON形式的数据
JSON.stringify使用注意事项
转换属性值中有 toJSON 方法,慎用
自定义 “toJSON”
一、像toString进行字符串转换,对象也可以提供toJSON方法来进行 JSON 转换。如果可用,JSON.stringify会自动调用它。
【示例1】
let room = {
number: 23
};
let meetup = {
title: "Conference",
date: new Date(Date.UTC(2017, 0, 1)),
room
};
alert( JSON.stringify(meetup) );
/*
{
"title":"Conference",
"date":"2017-01-01T00:00:00.000Z", // date变成了一个字符串。这是因为所有日期都有一个内置的toJSON方法来返回这种类型的字符串。
"room": {"number":23} // (2)
}
*/
二、现在让我们为对象room添加一个自定义的toJSON:
let room = {
number: 23,
toJSON() {
return this.number;
}
};
let meetup = {
title: "Conference",
room
};
alert( JSON.stringify(room) ); // 23
alert( JSON.stringify(meetup) );
/*
{
"title":"Conference",
"room": 23
}
*/
三、toJSON既可以用于直接调用JSON.stringify(room),也可以用于当room嵌套在另一个编码对象中时。
转换
一、转换值中如果有 toJSON 方法,该方法返回的值将会是最后的序列化结果
// toJSON
let toJsonMyIntro = {
name: "Gopal",
age: 25,
like: "FE",
toJSON: function () {
return "前端";
},
};
console.log(JSON.stringify(toJsonMyIntro)); // "前端"
被转换值中有 undefined、任意的函数以及 symbol 值,慎用
原理
一、JSON 是语言无关的纯数据规范,因此一些特定于 JavaScript 的对象属性会被JSON.stringify跳过。
即:
- 函数属性(方法)。
- Symbol 类型的属性。
- 存储undefined的属性。
1、如果这不是我们想要的方式,我们可以使用自定义转换方式。let user = { sayHi() { // 被忽略 alert("Hello"); }, [Symbol("id")]: 123, // 被忽略 something: undefined // 被忽略 }; alert( JSON.stringify(user) ); // {}(空对象)
转换
一、被转换值分为两种情况
1、一种是数组对象,undefined、任意的函数以及 symbol 值会被转换成 null
2、一种是非数组对象,在序列化的过程中会被忽略 ```javascriptJSON.stringify([undefined, Object, Symbol("")]); // '[null,null,null]'
JSON.stringify({ x: undefined, y: Object, z: Symbol(“”) }); // ‘{}’
二、对于这种情况,我们可以使用 JSON.stringify 的第二个参数,使其达到符合我们的预期
```javascript
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)"}
包含循环引用的对象,慎用
一、JSON.stringify重要的限制:不得有循环引用。
【示例1】
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: ["john", "ann"]
};
meetup.place = room; // meetup 引用了 room
room.occupiedBy = meetup; // room 引用了 meetup
JSON.stringify(meetup); // Error: Converting circular structure to JSON
1、在这里,转换失败了,因为循环引用:room.occupiedBy引用了meetup,meetup.place引用了room:
转换
一、【示例1】有如下代码
let objA = {
name: "Gopal",
}
let objB = {
age: 25,
}
objA.age = objB
objB.name = objA
JSON.stringify(objA)
1、会报以下错误:
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.parse:解析 JSON 的方法
一、要解码 JSON 字符串,我们需要另一个方法JSON.parse。
二、语法:
let value = JSON.parse(str, [reviver]);
- str:要解析的 JSON 字符串。
- reviver:可选的函数 function(key,value),该函数将为每个(key, value)对调用,并可以对值进行转换。
【示例1】
// 字符串化数组
let numbers = "[0, 1, 2, 3]";
numbers = JSON.parse(numbers);
alert( numbers[1] ); // 1
【示例2】对于嵌套对象:
let userData = '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }';
let user = JSON.parse(userData);
alert( user.friends[1] ); // 1
三、JSON 可能会非常复杂,对象和数组可以包含其他对象和数组。但是它们必须遵循相同的 JSON 格式。
四、以下是手写 JSON 时的典型错误(有时我们必须出于调试目的编写它):
let json = `{
name: "John", // 错误:属性名没有双引号
"surname": 'Smith', // 错误:值使用的是单引号(必须使用双引号)
'isAdmin': false // 错误:键使用的是单引号(必须使用双引号)
"birthday": new Date(2000, 2, 3), // 错误:不允许使用 "new",只能是裸值
"friends": [0,1,2,3] // 这个没问题
}`;
五、JSON 不支持注释。向 JSON 添加注释无效。
六、还有另一种名为JSON5的格式,它允许未加引号的键,也允许注释等。但这是一个独立的库,不在语言的规范中。
七、常规的 JSON 格式严格,并不是因为它的开发者很懒,而是为了实现简单,可靠且快速地实现解析算法。
使用 reviver
一、想象一下,我们从服务器上获得了一个字符串化的meetup对象。
1、它看起来像这样:
// title: (meetup title), date: (meetup date)
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';
2、现在我们需要对它进行反序列(deserialize),把它转换回 JavaScript 对象。
3、让我们通过调用JSON.parse来完成:
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';
let meetup = JSON.parse(str);
alert( meetup.date.getDate() ); // Error!
(1)报错了!
(2)meetup.date的值是一个字符串,而不是Date对象。JSON.parse怎么知道应该将字符串转换为Date呢?
4、让我们将 reviver 函数传递给JSON.parse作为第二个参数,该函数按照“原样”返回所有值,但是date会变成Date:
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';
let meetup = JSON.parse(str, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
alert( meetup.date.getDate() ); // 现在正常运行了!
5、顺便说一下,这也适用于嵌套对象:
let schedule = `{
"meetups": [
{"title":"Conference","date":"2017-11-30T12:00:00.000Z"},
{"title":"Birthday","date":"2017-04-18T12:00:00.000Z"}
]
}`;
schedule = JSON.parse(schedule, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
alert( schedule.meetups[1].date.getDate() ); // 正常运行了!