第23章 JSON
文章目录
本章内容
- 理解 JSON 语法
- 解析 JSON
- JSON 序列化
2006年 Douglas Crockford 在国际互联网工程任务组制定了 JavaScript 对象简谱( JSON, JavaScript Object Notation)标准。实际上,JSON 早在 2001 年就开始使用了。JSON 是 JavaScript 的严格子集,利用 JavaScript 中的几种模式来表示结构化数据。
理解 JSON 最关键的一点是要把它当成一种数据格式,而不是编程语言。JSON 不属于 JavaScript,它们只是拥有相同的语法而已。很多语言都有解析和序列化 JSON 的内置能力。
23.1、语法
JSON 语法支持表示 3 种类型的值。
- 简单值:字符串、数值、布尔值和 null 可以在 JSON 中出现,就像在 JavaScript 中一样。特殊值 undefined 不可以。
- 对象:对象表示有序键/值对。每个值可以是简单值,也可以是复杂类型。
- 数组:数组表示可以通过数值索引访问的值的有序列表。数组的值可以是任意类型,包括简单值、对象,甚至其他数组。
23.1.1 简单值
最简单的 JSON 可以是一个数值,数值和字符串等。
4
“Hello World”
JavaScript 字符串与 JSON 字符串的主要区别是,JSON 字符串必须使用双引号(单引号会导致语法错误)。
布尔值和 null 本身也是有效的 JSON 值。不过,实践中更多使用 JSON 表示比较复杂的数据结构,其中会包含简单值。
23.1.2 对象
对象使用与 JavaScript 对象字面量略为不同的方式表示。JavaScript 中的对象字面量:
let person = {
name: "Nicholas",
age: 29
};
let object = { // js中属性名加上引号,等同于上面的效果
"name": "Nicholas",
"age" : 29
};
但 JSON 中的对象必须使用双引号把属性名包围起来。JSON 表示相同的对象的语法是:
{
"name": "Nicholas",
"age": 29
}
与 JavaScript 对象字面量相比,JSON 主要有三处不同。
- 没有变量声明( JSON 中没有变量)。
- 最后没有分号(不需要,因为不是 JavaScript 语句)。
- 用引号将属性名包围起来才是有效的 JSON。
属性的值可以是简单值或复杂数据类型值,后者可以在对象中再嵌入对象,比如:
{
"name": "Nicholas",
"age": 29,
"school": {
"name": "Merrimack College",
"location": "North Andover, MA"
}
}
23.1.3 数组
数组在 JSON 中使用 JavaScript 的数组字面量形式表示。例如,以下是一个 JavaScript 数组:
let values = [25, "hi", true];
在 JSON 中可以使用类似语法表示相同的数组,同样,这里没有变量,也没有分号。
[25, "hi", true]
数组和对象可以组合使用,以表示更加复杂的数据结构。即数组中的元素可以是对象,或对象中的属性值可以为数组。
23.2、解析与序列化
23.2.1 JSON 对象
ECMAScript 5 增加了 JSON 全局对象,正式引入解析 JSON 的能力。这个对象在所有主流浏览器中都得到了支持。JSON 对象有两个方法:stringify()
和 parse()
。这两个方法分别可以将 JavaScript 序列化为 JSON 字符串,以及将 JSON 解析为原生 JavaScript 值。
stringify()
方法:把一个 JavaScript 对象序列化为一个不包含空格或缩进 JSON 字符串。
let book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas",
"Matt Frisbie"
],
edition: 4,
year: 2017
};
let jsonText = JSON.stringify(book);
console.log(jsonText);
//{"title":"Professional JavaScript","authors":["Nicholas C. Zakas","Matt Frisbie"],"edition":4,"year":2017}
在序列化 JavaScript 对象时,所有函数和原型成员都会有意地在结果中省略。此外,值为 undefined 的任何属性也会被跳过。最终得到的就是所有实例属性均为有效 JSON 数据类型的表示。
parse()
方法:将 JSON 字符串转化为相应的 JavaScript 值。
let bookCopy = JSON.parse(jsonText);
book 和 bookCopy 是两个完全不同的对象,没有任何关系。但是它们拥有相同的属性和值。如果给 JSON.parse()
传入的 JSON 字符串无效,则会导致抛出错误。
23.2.2 序列化选项
JSON.stringify()
方法除接收序列化的对象参数外,还可以接收两个参数。用于指定其序列化 JavaScript 对象的方式。
-
第一个参数:序列化的对象(必输)
-
第二个参数:过滤器结果,可以是一个数组或函数(可选)
-
第三个参数:数字或字符串,控制输出字符串缩进和空格(可选)
01. 过滤结果
- 第二个参数是一个数组:那么
JSON.stringify()
返回的结果只会包含该数组中列出的对象属性。
let book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas",
"Matt Frisbie"
],
edition: 4,
year: 2017
};
let jsonText = JSON.stringify(book, ["title", "edition"]);
console.log(jsonText); // {"title":"Professional JavaScript","edition":4}
- 第二个参数是一个函数(称之为替代函数(replacer)):提供的函数接收两个参数:属性名(key)和属性值(value)。可以根据这个 key 决定要对相应属性执行什么操作。返回的值就是相应 key 应该包含的结果。注意,返回 undefined 会导致属性被忽略。
let book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas",
"Matt Frisbie"
],
edition: 4,
year: 2017
};
let jsonText = JSON.stringify(book, (key, value) => {
switch (key) {
case "authors":
return value.join(",") // 用 ,分隔 authors 属性值
case "year":
return 5000; // year 属性值改为 5000
case "edition":
return undefined; // edition 属性值改为 undefined 所以不显示
default:
return value; // 其他属性直接返回结果
}
});
console.log(jsonText);
// {"title":"Professional JavaScript","authors":"Nicholas C. Zakas,Matt Frisbie","year":5000}
【注意📢】第一次调用这个函数实际上会传入空字符串 key,值是 book 对象。函数过滤器会应用到要序列化的对象所包含的所有对象(例如下面实例中的a属性),会递归处理内部的每一个对象。可通过下面的实例调试看到每一步执行细节。
![](https://i-blog.csdnimg.cn/blog_migrate/ee73793f174a1aa42edb127ff7493eb4.png)
02. 字符串缩进
- 第三个参数是一个数值:表示每一级缩进的空格数。
let book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas",
"Matt Frisbie"
],
edition: 4,
year: 2017
};
let jsonText = JSON.stringify(book, null, 4); // 每级缩进4个空格
console.log(jsonText);
// console.log(jsonText) 输出结果
{
"title": "Professional JavaScript",
"authors": [
"Nicholas C. Zakas",
"Matt Frisbie"
],
"edition": 4,
"year": 2017
}
除了缩进,JSON.stringify()
方法还为方便阅读插入了换行符(有缩进就强行换行)。最大缩进值为10,大于10的值会自动设置为10。
- 第三个参数是一个字符串:JSON 字符串中就会使用这个字符串而不是空格来缩进。
let jsonText = JSON.stringify(book, null, "--" );
// console.log(jsonText) 输出结果
{
--"title": "Professional JavaScript",
--"authors": [
----"Nicholas C. Zakas",
----"Matt Frisbie"
--],
--"edition": 4,
--"year": 2017
}
使用字符串时同样有 10 个字符的长度限制。如果字符串长度超过 10,则会在第 10 个字符处截断。
03. toJSON() 方法
如果对象需要在 JSON.stringify()
之上自定义 JSON 序列化,可以在要序列化的对象中添加 toJSON()
方法。如果对象有 toJSON 方法,JSON.stringify()
就会调用对象的 toJSON()
方法,以 toJSON()
方法返回的值为序列化值 。
let book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas",
"Matt Frisbie"
],
edition: 4,
year: 2017,
toJSON: function () { // toJSON 方法
return this.title;
}
};
let jsonText = JSON.stringify(book);
console.log(jsonText); // "Professional JavaScript"
toJSON()
函数是 JavaScript 构建类时重要的工具。通过这种方式,您可以控制 JavaScript 如何将你的类实例序列化为 json 字符串。
箭头函数不能用来定义 toJSON()
方法。主要原因是箭头函数的词法作用域是全局作用域,在这种情况下不合适。
toJSON()
方法可以与过滤函数一起使用,把对象传给 JSON.stringify()
时会执行如下步骤:
- 对象有
toJSON()
方法则调用toJSON()
方法获取实际的值, 否则使用默认的序列化。 - 如果提供了第二个参数,则应用过滤。传入过滤函数的值就是第 1 步返回的值。
- 第 2 步返回的每个值都会相应地进行序列化。
- 如果提供了第三个参数,则相应地进行缩进。
如下例子结合了三个参数去序列化一个对象。
let book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas",
"Matt Frisbie"
],
edition: 4,
year: 2017,
a: {
b: 11,
toJSON: function () { // toJSON 方法
return {
rea: 99,
reb: "nihao",
rec: {
reca: 3,
}
}
}
}
};
let jsonText = JSON.stringify(book, (key, value) => {
switch (key) {
default:
console.log("key:" + key + " # value:" + value);
return value;
}
}, "--");
console.log(jsonText);
输出结果:
![](https://i-blog.csdnimg.cn/blog_migrate/12567f83aabfc281baa0c44f05114830.png)
23.2.3 解析选项
JSON.parse()
方法除了接受必输的 JSON 字符串外,还可选接受第二个函数参数,这个函数会针对每个键/ 值对都调用一次。称为还原函数(reviver)。该函数也接收两个参数,属性名(key)和属性值 (value),也需要返回值。
如果还原函数返回 undefined,则结果中就会删除相应的键。如果返回了其他任何值,则该值就会成为相应键的值插入到结果中。还原函数经常被用于把日期字符串转换为 Date 对象。
let book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas",
"Matt Frisbie"
],
edition: 4,
year: 2017,
releaseDate: new Date(2017, 11, 1)
};
let jsonText = JSON.stringify(book);
console.log(jsonText); // {"title":"Professional JavaScript","authors":["Nicholas C. Zakas","Matt Frisbie"],"edition":4,"year":2017,"releaseDate":"2017-11-30T16:00:00.000Z"}
let bookCopy = JSON.parse(jsonText,
(key, value) => key == "releaseDate" ? new Date(value) : value);
console.log(bookCopy.releaseDate.getFullYear()); // 2017
23.3、小结
JSON 是一种轻量级数据格式,可以方便地表示复杂数据结构。这个格式使用 JavaScript 语法的一个子集表示对象、数组、字符串、数值、布尔值和 null。虽然 XML 也能胜任同样的角色,但 JSON 更简洁,JavaScript 支持也更好。更重要的是,所有浏览器都已经原生支持全局 JSON 对象。
ECMAScript 5 定义了原生 JSON 对象,用于将 JavaScript 对象序列化为 JSON 字符串,以及将 JSON 数组解析为 JavaScript 对象。JSON.stringify()
和 JSON.parse()
方法分别用于实现这两种操作。 这两个方法都有一些选项可以用来改变默认的行为,以实现过滤或修改流程。
23.3.1 原生 JSON 对象常用户对象的深拷贝
对象的深浅拷贝可以参考:JS 中对象的深浅拷贝
let obj1 = {
a: "a",
b: [
"b1",
"b2"
],
c: true,
d: 2017,
e: {
e1:1,
e2:{
e2a:3
}
}
};
let obj2 = JSON.parse(JSON.stringify(obj1))
console.log("obj1 === obj2?",obj1 === obj2);
console.log("obj1:",JSON.stringify(obj1));
console.log("obj2:",JSON.stringify(obj2));
obj1.b[0]="b99";
obj2.e.e2.e2a = 99;
console.log("obj1修改了b[0]:",JSON.stringify(obj1));
console.log("obj2修改了e.e2.e2a:",JSON.stringify(obj2));
![JSON实现对象深拷贝](https://i-blog.csdnimg.cn/blog_migrate/1c5276c3c5943eee29004ed45605f37b.png)