使用 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,以及如何訪問其中鎖定的資料。在下一篇文章中,我們將為你提供一些測試,你可以用來檢查你對所有這些資訊的理解和記憶程度。

另見