使用 JSON
JavaScript 对象表示法 (JSON) 是一种基于 JavaScript 对象语法表示结构化数据的标准文本格式。它通常用于在 Web 应用程序中传输数据(例如,将一些数据从服务器发送到客户端,以便在网页上显示,反之亦然)。你经常会遇到它,所以在这篇文章中,我们将向你介绍使用 JavaScript 处理 JSON 所需的一切,包括解析 JSON 以便访问其中的数据,以及创建 JSON。
不,真的,JSON 是什么?
JSON 是一种遵循 JavaScript 对象语法的文本数据格式。它将结构化数据表示为字符串,这在你想通过网络传输数据时非常有用。尽管它与 JavaScript 对象字面量语法非常相似,但它可以独立于 JavaScript 使用。许多编程环境都具备读取(解析)和生成 JSON 的能力。在 JavaScript 中,解析和生成 JSON 的方法由 JSON
对象提供。
注意:将字符串转换为原生对象称为反序列化,而将原生对象转换为字符串以便通过网络传输称为序列化。
JSON 字符串可以存储在它自己的文件中,它基本上只是一个扩展名为 .json
的文本文件,MIME 类型为 application/json
。
JSON 结构
如上所述,JSON 是一个字符串,其格式非常类似于 JavaScript 对象字面量格式。以下是一个有效的 JSON 字符串,表示一个对象。请注意,它也是一个有效的 JavaScript 对象字面量 — 只是有一些额外的语法限制。
{
"squadName": "Super hero squad",
"homeTown": "Metro City",
"formed": 2016,
"secretBase": "Super tower",
"active": true,
"members": [
{
"name": "Molecule Man",
"age": 29,
"secretIdentity": "Dan Jukes",
"powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]
},
{
"name": "Madame Uppercut",
"age": 39,
"secretIdentity": "Jane Wilson",
"powers": [
"Million tonne punch",
"Damage resistance",
"Superhuman reflexes"
]
},
{
"name": "Eternal Flame",
"age": 1000000,
"secretIdentity": "Unknown",
"powers": [
"Immortality",
"Heat Immunity",
"Inferno",
"Teleportation",
"Interdimensional travel"
]
}
]
}
如果你将此 JSON 作为字符串加载到你的 JavaScript 程序中,你可以将其解析为普通对象,然后使用我们在JavaScript 对象基础知识文章中讨论过的相同点/方括号表示法访问其中的数据。例如:
superHeroes.homeTown;
superHeroes.members[1].powers[2];
- 首先,我们有变量名 —
superHeroes
。 - 在其中,我们想访问
members
属性,所以我们使用.members
。 members
包含一个由对象组成的数组。我们想访问数组中的第二个对象,所以我们使用[1]
。- 在这个对象中,我们想访问
powers
属性,所以我们使用.powers
。 powers
属性中是一个数组,其中包含选定英雄的超能力。我们想要第三个,所以我们使用[2]
。
关键是,使用 JSON 并没有什么特别之处;在你将其解析为 JavaScript 对象之后,你就可以像处理使用相同对象字面量语法声明的对象一样处理它。
注意:我们已将上述 JSON 在我们的 JSONTest.html 示例中作为变量提供(请参阅源代码)。尝试加载它,然后通过浏览器的 JavaScript 控制台访问变量中的数据。
作为 JSON 的数组
上面我们提到 JSON 文本基本上看起来像字符串中的 JavaScript 对象。我们也可以将数组转换为/从 JSON。以下示例是完全有效的 JSON:
[
{
"name": "Molecule Man",
"age": 29,
"secretIdentity": "Dan Jukes",
"powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]
},
{
"name": "Madame Uppercut",
"age": 39,
"secretIdentity": "Jane Wilson",
"powers": [
"Million tonne punch",
"Damage resistance",
"Superhuman reflexes"
]
}
]
你必须从数组索引开始访问数组项(在其解析版本中),例如 superHeroes[0].powers[0]
。
JSON 也可以包含单个原始值。例如,29
、"Dan Jukes"
或 true
都是有效的 JSON。
JSON 语法限制
如前所述,任何 JSON 都是有效的 JavaScript 字面量(对象、数组、数字等)。然而,反之则不然 — 并非所有 JavaScript 对象字面量都是有效的 JSON。
- JSON 只能包含可序列化的数据类型。这意味着:
- 对于原始类型,JSON 可以包含字符串字面量、数字字面量、
true
、false
和null
。值得注意的是,它不能包含undefined
、NaN
或Infinity
。 - 对于非原始类型,JSON 可以包含对象字面量和数组,但不能包含函数或任何其他对象类型,例如
Date
、Set
和Map
。JSON 中的对象和数组需要进一步包含有效的 JSON 数据类型。
- 对于原始类型,JSON 可以包含字符串字面量、数字字面量、
- 字符串必须用双引号括起来,而不是单引号。
- 数字必须以十进制表示法书写。
- 对象的每个属性必须是
"key": value
的形式。属性名称必须是双引号括起来的字符串字面量。不允许使用特殊的 JavaScript 语法,例如方法,因为方法是函数,而函数不是有效的 JSON 数据类型。 - 对象和数组不能包含尾随逗号。
- JSON 中不允许有注释。
即使是单个放错位置的逗号或冒号也可能导致 JSON 文件无效并使其失败。你应该小心验证你尝试使用的任何数据(尽管计算机生成的 JSON 不太可能包含错误,只要生成程序正常工作)。你可以使用 JSONLint 或 JSON-validate 等应用程序验证 JSON。
通过一个 JSON 示例进行操作
那么,让我们通过一个例子来展示我们如何在网站上利用一些 JSON 格式的数据。
入门
首先,复制我们的 heroes.html 和 style.css 文件到本地。后者包含一些简单的 CSS 来样式化我们的页面,而前者包含一些非常简单的 HTML 主体,以及一个 <script>
元素来包含我们将在本练习中编写的 JavaScript 代码。
<header>
...
</header>
<section>
...
</section>
<script>
// JavaScript goes here
</script>
我们的 JSON 数据已在 GitHub 上提供,网址为 https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json。
我们将把 JSON 加载到我们的脚本中,并使用一些巧妙的 DOM 操作来显示它,就像这样:
顶级函数
顶级函数如下所示:
async function populate() {
const requestURL =
"https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json";
const request = new Request(requestURL);
const response = await fetch(request);
const superHeroes = await response.json();
populateHeader(superHeroes);
populateHeroes(superHeroes);
}
为了获取 JSON,我们使用了一个名为 Fetch 的 API。这个 API 允许我们通过 JavaScript 向服务器发出网络请求来检索资源(例如图像、文本、JSON 甚至 HTML 片段),这意味着我们可以更新内容的小部分而无需重新加载整个页面。
在我们的函数中,前四行使用 Fetch API 从服务器获取 JSON:
- 我们声明
requestURL
变量来存储 GitHub URL。 - 我们使用 URL 初始化一个新的
Request
对象。 - 我们使用
fetch()
函数发出网络请求,它返回一个Response
对象。 - 我们使用
Response
对象的json()
函数将响应检索为 JSON。
注意: fetch()
API 是异步的。你可以在我们的异步 JavaScript 模块中详细了解异步函数,但现在,我们只需说明我们需要在使用 fetch API 的函数名称前添加关键字 async
,并在调用任何异步函数前添加关键字 await
。
完成所有这些后,superHeroes
变量将包含基于 JSON 的 JavaScript 对象。然后,我们将该对象传递给两个函数调用——第一个函数用正确的数据填充 <header>
,而第二个函数为团队中的每个英雄创建一个信息卡,并将其插入到 <section>
中。
填充 header
既然我们已经检索到 JSON 数据并将其转换为 JavaScript 对象,那么让我们通过编写上面提到的两个函数来利用它。首先,在前面的代码下方添加以下函数定义:
function populateHeader(obj) {
const header = document.querySelector("header");
const myH1 = document.createElement("h1");
myH1.textContent = obj.squadName;
header.appendChild(myH1);
const myPara = document.createElement("p");
myPara.textContent = `Hometown: ${obj.homeTown} // Formed: ${obj.formed}`;
header.appendChild(myPara);
}
这里我们首先使用 createElement()
创建一个 h1 元素,将其 textContent
设置为等于对象的 squadName
属性,然后使用 appendChild()
将其附加到 header。然后,我们对一个段落执行非常类似的操作:创建它,设置其文本内容并将其附加到 header。唯一的区别是其文本被设置为一个模板字面量,其中包含对象的 homeTown
和 formed
属性。
创建英雄信息卡
接下来,在代码底部添加以下函数,它创建并显示超级英雄卡片:
function populateHeroes(obj) {
const section = document.querySelector("section");
const heroes = obj.members;
for (const hero of heroes) {
const myArticle = document.createElement("article");
const myH2 = document.createElement("h2");
const myPara1 = document.createElement("p");
const myPara2 = document.createElement("p");
const myPara3 = document.createElement("p");
const myList = document.createElement("ul");
myH2.textContent = hero.name;
myPara1.textContent = `Secret identity: ${hero.secretIdentity}`;
myPara2.textContent = `Age: ${hero.age}`;
myPara3.textContent = "Superpowers:";
const superPowers = hero.powers;
for (const power of superPowers) {
const listItem = document.createElement("li");
listItem.textContent = power;
myList.appendChild(listItem);
}
myArticle.appendChild(myH2);
myArticle.appendChild(myPara1);
myArticle.appendChild(myPara2);
myArticle.appendChild(myPara3);
myArticle.appendChild(myList);
section.appendChild(myArticle);
}
}
首先,我们将 JavaScript 对象的 members
属性存储在一个新变量中。此数组包含多个对象,这些对象包含每个英雄的信息。
接下来,我们使用 for...of
循环遍历数组中的每个对象。对于每个对象,我们:
- 创建几个新元素:一个
<article>
、一个<h2>
、三个<p>
和一个<ul>
。 - 设置
<h2>
以包含当前英雄的name
。 - 用他们的
secretIdentity
、age
以及一句“超能力:”来介绍列表中的信息。 - 将
powers
属性存储在另一个名为superPowers
的新常量中 — 它包含一个列出当前英雄超能力的数组。 - 使用另一个
for...of
循环遍历当前英雄的超能力 — 对于每个超能力,我们创建一个<li>
元素,将超能力放入其中,然后使用appendChild()
将listItem
放入<ul>
元素 (myList
) 中。 - 我们做的最后一件事是将
<h2>
、<p>
和<ul>
附加到<article>
(myArticle
) 中,然后将<article>
附加到<section>
中。附加的顺序很重要,因为这是它们在 HTML 中显示的顺序。
注意:如果你在尝试使示例正常工作时遇到问题,请尝试参考我们的 heroes-finished.html 源代码(也可以查看其运行情况。)
注意:如果你在遵循我们用于访问 JavaScript 对象的点/方括号表示法时遇到困难,可以尝试在另一个选项卡或文本编辑器中打开 superheroes.json 文件,并在查看我们的 JavaScript 时参考它。你也应该参考我们的 JavaScript 对象基础知识文章,以获取有关点和方括号表示法的更多信息。
调用顶级函数
最后,我们需要调用我们的顶级 populate()
函数:
populate();
在对象和文本之间转换
上面的示例在访问 JavaScript 对象方面很简单,因为我们使用 response.json()
将网络响应直接转换为 JavaScript 对象。
但有时我们没有那么幸运——有时我们收到一个原始 JSON 字符串,我们需要自己将其转换为对象。当我们想通过网络发送一个 JavaScript 对象时,我们需要在发送之前将其转换为 JSON(一个字符串)。幸运的是,这两个问题在 Web 开发中非常常见,因此浏览器中提供了一个内置的 JSON 对象,其中包含以下两种方法:
parse()
:接受一个 JSON 字符串作为参数,并返回相应的 JavaScript 对象。stringify()
:接受一个对象作为参数,并返回等效的 JSON 字符串。
你可以在我们的 heroes-finished-json-parse.html 示例中看到第一个的实际应用(请参阅源代码)——这与我们之前构建的示例完全相同,只是:
- 我们通过调用响应的
text()
方法来以文本而不是 JSON 的形式检索响应。 - 然后我们使用
parse()
将文本转换为 JavaScript 对象。
关键代码片段在这里:
async function populate() {
const requestURL =
"https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json";
const request = new Request(requestURL);
const response = await fetch(request);
const superHeroesText = await response.text();
const superHeroes = JSON.parse(superHeroesText);
populateHeader(superHeroes);
populateHeroes(superHeroes);
}
正如你可能猜到的,stringify()
的工作方式恰恰相反。尝试将以下行逐行输入到浏览器的 JavaScript 控制台中,以查看其运行情况:
let myObj = { name: "Chris", age: 38 };
myObj;
let myString = JSON.stringify(myObj);
myString;
这里我们正在创建一个 JavaScript 对象,检查它包含什么,使用 stringify()
将其转换为 JSON 字符串——将返回值保存在一个新变量中——然后再次检查它。
总结
在本课程中,我们向你介绍了如何在程序中使用 JSON,包括如何创建和解析 JSON,以及如何访问其中锁定的数据。在下一篇文章中,我们将为你提供一些测试,你可以用来检查你对所有这些信息的理解和记忆程度。