使用 JSON

JavaScript 对象表示法 (JSON) 是一种基于 JavaScript 对象语法表示结构化数据的标准文本格式。它通常用于在 Web 应用程序中传输数据(例如,将一些数据从服务器发送到客户端,以便在网页上显示,反之亦然)。你经常会遇到它,所以在这篇文章中,我们将向你介绍使用 JavaScript 处理 JSON 所需的一切,包括解析 JSON 以便访问其中的数据,以及创建 JSON。

预备知识 了解 HTMLCSS 基础,熟悉前面课程中介绍的 JavaScript 基础。
学习成果
  • JSON 是什么 — 一种非常常用的数据格式,基于 JavaScript 对象语法。
  • JSON 也可以包含数组。
  • 使用 Web API 中可用的机制(例如,Fetch API 中的 Response.json())将 JSON 检索为 JavaScript 对象。
  • 使用方括号和点语法访问 JSON 数据中的值。
  • 使用 JSON.parse()JSON.stringify() 在对象和文本之间进行转换。

不,真的,JSON 是什么?

JSON 是一种遵循 JavaScript 对象语法的文本数据格式。它将结构化数据表示为字符串,这在你想通过网络传输数据时非常有用。尽管它与 JavaScript 对象字面量语法非常相似,但它可以独立于 JavaScript 使用。许多编程环境都具备读取(解析)和生成 JSON 的能力。在 JavaScript 中,解析和生成 JSON 的方法由 JSON 对象提供。

注意:将字符串转换为原生对象称为反序列化,而将原生对象转换为字符串以便通过网络传输称为序列化

JSON 字符串可以存储在它自己的文件中,它基本上只是一个扩展名为 .json 的文本文件,MIME 类型为 application/json

JSON 结构

如上所述,JSON 是一个字符串,其格式非常类似于 JavaScript 对象字面量格式。以下是一个有效的 JSON 字符串,表示一个对象。请注意,它也是一个有效的 JavaScript 对象字面量 — 只是有一些额外的语法限制

json
{
  "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 对象基础知识文章中讨论过的相同点/方括号表示法访问其中的数据。例如:

js
superHeroes.homeTown;
superHeroes.members[1].powers[2];
  1. 首先,我们有变量名 — superHeroes
  2. 在其中,我们想访问 members 属性,所以我们使用 .members
  3. members 包含一个由对象组成的数组。我们想访问数组中的第二个对象,所以我们使用 [1]
  4. 在这个对象中,我们想访问 powers 属性,所以我们使用 .powers
  5. powers 属性中是一个数组,其中包含选定英雄的超能力。我们想要第三个,所以我们使用 [2]

关键是,使用 JSON 并没有什么特别之处;在你将其解析为 JavaScript 对象之后,你就可以像处理使用相同对象字面量语法声明的对象一样处理它。

注意:我们已将上述 JSON 在我们的 JSONTest.html 示例中作为变量提供(请参阅源代码)。尝试加载它,然后通过浏览器的 JavaScript 控制台访问变量中的数据。

作为 JSON 的数组

上面我们提到 JSON 文本基本上看起来像字符串中的 JavaScript 对象。我们也可以将数组转换为/从 JSON。以下示例是完全有效的 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 可以包含字符串字面量、数字字面量、truefalsenull。值得注意的是,它不能包含 undefinedNaNInfinity
    • 对于非原始类型,JSON 可以包含对象字面量和数组,但不能包含函数或任何其他对象类型,例如 DateSetMap。JSON 中的对象和数组需要进一步包含有效的 JSON 数据类型。
  • 字符串必须用双引号括起来,而不是单引号。
  • 数字必须以十进制表示法书写。
  • 对象的每个属性必须是 "key": value 的形式。属性名称必须是双引号括起来的字符串字面量。不允许使用特殊的 JavaScript 语法,例如方法,因为方法是函数,而函数不是有效的 JSON 数据类型。
  • 对象和数组不能包含尾随逗号
  • JSON 中不允许有注释。

即使是单个放错位置的逗号或冒号也可能导致 JSON 文件无效并使其失败。你应该小心验证你尝试使用的任何数据(尽管计算机生成的 JSON 不太可能包含错误,只要生成程序正常工作)。你可以使用 JSONLintJSON-validate 等应用程序验证 JSON。

注意:阅读完本节后,你可能还想通过 Scrimba 的JSON 评论 MDN 学习伙伴 交互式教程来补充你的学习,它提供了一些关于基本 JSON 语法以及如何在浏览器开发工具中查看 JSON 请求数据的有用指导。

通过一个 JSON 示例进行操作

那么,让我们通过一个例子来展示我们如何在网站上利用一些 JSON 格式的数据。

入门

首先,复制我们的 heroes.htmlstyle.css 文件到本地。后者包含一些简单的 CSS 来样式化我们的页面,而前者包含一些非常简单的 HTML 主体,以及一个 <script> 元素来包含我们将在本练习中编写的 JavaScript 代码。

html
<header>
...
</header>

<section>
...
</section>

<script>
// JavaScript goes here
</script>

我们的 JSON 数据已在 GitHub 上提供,网址为 https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json

我们将把 JSON 加载到我们的脚本中,并使用一些巧妙的 DOM 操作来显示它,就像这样:

Image of a document titled "Super hero squad" (in a fancy font) and subtitled "Hometown: Metro City // Formed: 2016". Three columns below the heading are titled "Molecule Man", "Madame Uppercut", and "Eternal Flame", respectively. Each column lists the hero's secret identity name, age, and superpowers.

顶级函数

顶级函数如下所示:

js
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 对象,那么让我们通过编写上面提到的两个函数来利用它。首先,在前面的代码下方添加以下函数定义:

js
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。唯一的区别是其文本被设置为一个模板字面量,其中包含对象的 homeTownformed 属性。

创建英雄信息卡

接下来,在代码底部添加以下函数,它创建并显示超级英雄卡片:

js
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 循环遍历数组中的每个对象。对于每个对象,我们:

  1. 创建几个新元素:一个 <article>、一个 <h2>、三个 <p> 和一个 <ul>
  2. 设置 <h2> 以包含当前英雄的 name
  3. 用他们的 secretIdentityage 以及一句“超能力:”来介绍列表中的信息。
  4. powers 属性存储在另一个名为 superPowers 的新常量中 — 它包含一个列出当前英雄超能力的数组。
  5. 使用另一个 for...of 循环遍历当前英雄的超能力 — 对于每个超能力,我们创建一个 <li> 元素,将超能力放入其中,然后使用 appendChild()listItem 放入 <ul> 元素 (myList) 中。
  6. 我们做的最后一件事是将 <h2><p><ul> 附加到 <article> (myArticle) 中,然后将 <article> 附加到 <section> 中。附加的顺序很重要,因为这是它们在 HTML 中显示的顺序。

注意:如果你在尝试使示例正常工作时遇到问题,请尝试参考我们的 heroes-finished.html 源代码(也可以查看其运行情况。)

注意:如果你在遵循我们用于访问 JavaScript 对象的点/方括号表示法时遇到困难,可以尝试在另一个选项卡或文本编辑器中打开 superheroes.json 文件,并在查看我们的 JavaScript 时参考它。你也应该参考我们的 JavaScript 对象基础知识文章,以获取有关点和方括号表示法的更多信息。

调用顶级函数

最后,我们需要调用我们的顶级 populate() 函数:

js
populate();

在对象和文本之间转换

上面的示例在访问 JavaScript 对象方面很简单,因为我们使用 response.json() 将网络响应直接转换为 JavaScript 对象。

但有时我们没有那么幸运——有时我们收到一个原始 JSON 字符串,我们需要自己将其转换为对象。当我们想通过网络发送一个 JavaScript 对象时,我们需要在发送之前将其转换为 JSON(一个字符串)。幸运的是,这两个问题在 Web 开发中非常常见,因此浏览器中提供了一个内置的 JSON 对象,其中包含以下两种方法:

  • parse():接受一个 JSON 字符串作为参数,并返回相应的 JavaScript 对象。
  • stringify():接受一个对象作为参数,并返回等效的 JSON 字符串。

你可以在我们的 heroes-finished-json-parse.html 示例中看到第一个的实际应用(请参阅源代码)——这与我们之前构建的示例完全相同,只是:

  • 我们通过调用响应的 text() 方法来以文本而不是 JSON 的形式检索响应。
  • 然后我们使用 parse() 将文本转换为 JavaScript 对象。

关键代码片段在这里:

js
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 控制台中,以查看其运行情况:

js
let myObj = { name: "Chris", age: 38 };
myObj;
let myString = JSON.stringify(myObj);
myString;

这里我们正在创建一个 JavaScript 对象,检查它包含什么,使用 stringify() 将其转换为 JSON 字符串——将返回值保存在一个新变量中——然后再次检查它。

总结

在本课程中,我们向你介绍了如何在程序中使用 JSON,包括如何创建和解析 JSON,以及如何访问其中锁定的数据。在下一篇文章中,我们将为你提供一些测试,你可以用来检查你对所有这些信息的理解和记忆程度。

另见