使用 JSON 資料

JavaScript Object Notation (JSON) 為將結構化資料 (structured data) 呈現為 JavaScript 物件的標準格式,常用於網站上的資料呈現、傳輸 (例如將資料從伺服器送至用戶端,以利顯示網頁)。你應該會常常遇到,因此本文將說明 JavaScript 搭配 JSON 時所應知道的觀念,包含如何在 JSON 物件中存取資料項目,並寫出你自己的 JSON。

必要條件: 基礎的計算機素養、了解 HTML 與 CSS 的基本概念、熟悉 JavaScript (參閱〈First steps〉與〈Building blocks〉) 與 OOJS 基本概念 (參閱〈Introduction to objects (en-US)〉)。
主旨: 了解應如何使用 JSON 格式所儲存的資料,建立自己的 JSON 物件。

說真的,到底什麼是 JSON?

JSON 是依照 JavaScript 物件語法的資料格式,經 Douglas Crockford 推廣普及。雖然 JSON 是以 JavaScript 語法為基礎,但可獨立使用,且許多程式設計環境亦可讀取 (剖析) 並產生 JSON。

JSON 可能是物件或字串。當你想從 JSON 中讀取資料時,JSON 可作為物件;當要跨網路傳送 JSON 時,就會是字串。這不是什麼大問題 — JavaScript 提供全域 JSON 物件,其內的函式可進行切換。

JSON 物件可儲存於其自有的檔案中,基本上就是副檔名為 .json 的文字檔案,以及 application/jsonMIME type

JSON 的架構

我們剛提到「JSON 物件基本上就是 JavaScript 物件」,而這敘述在大多數情況下都對。如同標準的 JavaScript 物件,你當然可在 JSON 之內加入相同的基本資料類型,如字串、數字、陣列、布林值,以及其他物件,接著同樣能再建構出資料繼承,如:

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"
      ]
    }
  ]
}

舉例來說,如果將此物件載入至 JavaScript 程式並將之儲存為「superHeroes」變數,如同〈JavaScript 物件基本概念〉一文中提過的,接著能以相同的 存取其內部的資料,如下:

js
superHeroes.hometown;
superHeroes["active"];

若要順著繼承往下存取資料,只要將必要的屬性名稱與陣列索引「鍊」在一起即可。舉例來說,如果要存取成員列表中的第二位英雄的第三項超能力,你必須:

js
superHeroes["members"][1]["powers"][2];
  1. 首先要有變數名稱 — superHeroes
  2. 要在變數中存取 members 屬性,所以用 ["members"]
  3. members 包含由物件產生陣列。我們要存取陣列中的第二個物件,所以用 [1]
  4. 在此物件中,我們要存取 powers 屬性,所以用 ["powers"]
  5. powers 屬性中有 1 個陣列具備所選超級英雄的能力。我們要選第三種能力,所以用 [2]

備註: 我們在 JSONText.html 範例 (參閱原始碼) 的變數中,示範上述可用的 JSON。你可在自己瀏覽器的 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"
    ]
  }
]

上面程式碼絕對是有效的 JSON。你可用陣列指數為開頭來存取陣列項目,例如 [0]["powers"][0]

其他附註

  • JSON 是純粹的資料格式 — 僅具備屬性,而無函式。
  • JSON 需要雙引號,才能使用\有效。所以最安全的方法就是以雙引號撰寫之。
  • 單一個逗號或冒號放錯位置,也會讓 JSON 檔案出錯而無法運作。你應仔細檢視所有要使用的資料 (只要產生器程式能正確運作,由電腦產生的 JSON 也就不容易出錯)。你可透過如 JSONLint 的應用程式檢驗 JSON。
  • 不限於陣列或物件,只要是符合標準 JSON 物件形式的任何資料,都可以夾帶進 JSON 檔案中。因此,單一字串或數字也可能是有效的 JSON 物件,但不一定有用就是了...

主動學習:完成 JSON 範例

現在就試著在網站上,透過某些 JSON 資料完成範例吧。

入門

在開始之前,先複製我們的 heroes.htmlstyle.css 到你的本端硬碟中。後者包含某些簡易的 CSS 可塑造網頁風格;前者則提供極簡單主體 HTML:

html
<header></header>

<section></section>

加上 <script> 元素,才能納入稍後會在此習題中寫出來的 JavaScript 程式碼。目前只有 2 行程式碼,用以取得 <header> (en-US)<section> 元素的參考,並將之儲存於變數之中:

js
var header = document.querySelector("header");
var section = document.querySelector("section");

你可到 GitHub 上找到此 JSON 資料:https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json

接著載入到頁面之中,並使用某些有趣的 DOM 操控 (DOM manipulation) 來顯示,如下:

載入我們的 JSON

若要將 JSON 載入至頁面,就要透過 XMLHttpRequest API (通常稱為 XHR)。此是極好用的 JavaScript 物件,可讓網路請求透過 JavaScript (例如圖片、文字、JSON,甚至 HTML 片段) 來檢索伺幅器的資源,這也代表我們不需載入整個頁面,就能更新小部分的內容。如此可讓網頁反應速度更快;聽起來很棒吧?但可惜本文無法再深入講解更多細節。

  1. 一開始,我們先針對要在變數中檢索的 JSON 檔案,將其網址儲存起來。把下列程式碼加到你 JavaScript 程式碼的最下方:
    js
    var requestURL =
      "https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json";
    
  2. 為了建立請求,我們必須透過 new 關鍵字,先從 XMLHttpRequest 建構子建立新的請求物件實例。把下列加到最後一行:
    js
    var request = new XMLHttpRequest();
    
  3. 現在用 open() (en-US) 函式開啟新的請求。加入下列程式碼:
    js
    request.open("GET", requestURL);
    
    這樣就顧到至少 2 個參數。當然也有其他參數可選擇。但這個簡易範例只需要 2 個強制參數:
    • 在設立網路請求時,應使用 HTTP 函式。因為這裡只要檢索簡單的資料,所以用 GET 就可以。
    • URL 提供請求目的地 — 這也就是我們剛剛儲存的 JSON 檔案網址。
  4. 接著加入下面 2 行程式碼。我們為 JSON 設定了 responseType (en-US),告知伺服器應回傳 JSON 物件,再以 send() (en-US) 函式傳送請求:
    js
    request.responseType = "json";
    request.send();
    
  5. 最後就是等待由伺服器所回傳的反應,再接著處理。把下列程式碼加入現有程式碼的最下方:
    js
    request.onload = function () {
      var superHeroes = request.response;
      populateHeader(superHeroes);
      showHeroes(superHeroes);
    };
    

在這裡,我們將所獲得的響應 (可到 response (en-US) 屬性中找到) 儲存到 superHeroes 變數之中。此變數現在會納入我們的 JSON。接著再把此 JSON 檔案送到 2 個函式呼叫。第一個函式呼叫會將正確資料填入 <header>;第二個函式呼叫則會為團隊中的各個英文建立資訊卡,再插入至 <section> 內。

當於請求物件上觸發載入事件時,會執行一個事件處理器。我們就將程式碼包裹至此處理器之中 (參閱 onload (en-US)) — 只要成功回傳響應,就會觸發載入事件。之所以這樣做,是為了確保當我們要以 request.response 進行某件事時,此 request.response 絕對可用。

產生標頭

現在檢索過了 JSON 資料,接著就寫出上面參照過的 2 個函式來利用 JSON 資料吧。首先將下列函式定義加到先前的程式碼中:

js
function populateHeader(jsonObj) {
  var myH1 = document.createElement("h1");
  myH1.textContent = jsonObj["squadName"];
  header.appendChild(myH1);

  var myPara = document.createElement("p");
  myPara.textContent =
    "Hometown: " + jsonObj["homeTown"] + " // Formed: " + jsonObj["formed"];
  header.appendChild(myPara);
}

我們已經將參數命名為 jsonObj,所以在這個函式之內就要用 jsonObj 呼叫此參數。這裡先以 createElement() 建立 1 組 <h1> (en-US) 元素、將其 textContent (en-US) 指定為 JSON 的 squadName 屬性、透過 appendChild() (en-US) 將之附加到標頭。接著 <p> 元素依樣畫葫蘆一遍:建立、設定其文字內容、附加到標頭。唯一不同之處,就是將該文字設定為 1 組串接字串 (Concatenated string),其內包含 JSON 的 homeTownformed 屬性。

建立超級英雄的資訊卡片

現在將下列函式加到程式碼底端,用以建立並顯示超級英雄的卡片:

js
function showHeroes(jsonObj) {
  var heroes = jsonObj["members"];

  for (i = 0; i < heroes.length; i++) {
    var myArticle = document.createElement("article");
    var myH2 = document.createElement("h2");
    var myPara1 = document.createElement("p");
    var myPara2 = document.createElement("p");
    var myPara3 = document.createElement("p");
    var myList = document.createElement("ul");

    myH2.textContent = heroes[i].name;
    myPara1.textContent = "Secret identity: " + heroes[i].secretIdentity;
    myPara2.textContent = "Age: " + heroes[i].age;
    myPara3.textContent = "Superpowers:";

    var superPowers = heroes[i].powers;
    for (j = 0; j < superPowers.length; j++) {
      var listItem = document.createElement("li");
      listItem.textContent = superPowers[j];
      myList.appendChild(listItem);
    }

    myArticle.appendChild(myH2);
    myArticle.appendChild(myPara1);
    myArticle.appendChild(myPara2);
    myArticle.appendChild(myPara3);
    myArticle.appendChild(myList);

    section.appendChild(myArticle);
  }
}

我們先把 JSON 的 members 屬性儲存到新的變數中。此陣列所具備的多個物件,均包含了各個超級英雄的資訊。

接著我們以 for 迴圈循環陣列中的各個物件。針對每個物件都會:

  1. 建立數個新的元素:1 組 <article>、1 組 <h2>、3 組 <p>、1 組 <ul>
  2. <h2> 納入目前超級英雄的 name
  3. 接著 3 個段落分別是英雄的 secretIdentityageSuperpowers,在列表中帶出相關資訊。
  4. 另以新變數 superPowers 儲存 powers 屬性 — 其中包含 1 組陣列以列出目前英雄的超能力。
  5. 再用另一個 for 迴圈逐一巡過目前英雄的超能力。針對每一項超能力,我們再建立 1 組 <li> 元素,把超能力放進該元素之中,再透過 appendChild()listItem 放入 <ul> 元素之內 (myList)。
  6. 最後就是在 <article> (myArticle) 之內附加 <h2><p><ul>;再把 <article> 附加於 <section> 之內。這附加的順序極為重要,因為這也會是 HTML 中的顯示順序。

備註: 如果你無法讓此範例運作,可參閱我們的 heroes-finished.html 原始碼 (亦可看到實際執行情況。)

備註: 如果你無法用我們說過的點記法 (dot-)\括弧記法 (bracket notation) 來存取 JSON,則可用新分頁或自己的文字編輯器開啟 superheroes.json 檔案並參考之。你也可再回去看看 JavaScript 物件基礎概念 ,再次了解點\括弧記法。

物件與文字交互轉換

上述是存取 JSON 的簡易範例,因為我們設定要回傳響應的 XHR 已經是 JSON 格式。透過:

js
request.responseType = "json";

但有時候沒這麼好運。我們有時會接收到文字字串格式的 JSON 資料,且必須將之轉換為物件。且當我們要以某種訊息傳送 JSON 資料時,也必須將之轉換為字串才能正確運作。還好,這 2 種問題在 Web 開發過程中甚為常見。內建的 JSON 物件很早就新增到瀏覽器之中,且包含下列 2 種函式:

  • parse():接收文字字串形式的 JSON 物件作為參數,並回傳對應的物件。
  • stringify() (en-US):接收 JSON 物件作為參數,並回傳對等的文字字串形式。

你可到 heroes-finished-json-parse.html 範例 (參閱原始碼) 中看到第一個函式的運作情形。這其實跟我們先前範例所進行的事情一模一樣,不同之處在於我們設定 XHR 要回傳 JSON 為文字,接著再使用 parse() 轉換為實際的 JSON 物件。關鍵程式碼片段如下:

js
request.open("GET", requestURL);
request.responseType = "text"; // now we're getting a string!
request.send();

request.onload = function () {
  var superHeroesText = request.response; // get the string from the response
  var superHeroes = JSON.parse(superHeroesText); // convert it to an object
  populateHeader(superHeroes);
  showHeroes(superHeroes);
};

你可能會猜 stringify() 就是反過來運作了吧?可在瀏覽器的 JavaScript 主控台上輸入下列程式碼,看看其運作方式:

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

這樣就建立了 JSON 物件了。接著檢查內容物之後,就可透過 stringify() 將之轉換為字串。將回傳值儲存到新變數之中,再檢查一次即可。

摘要

我們透過本文簡單介紹了該如何在程式中使用 JSON、該如何建立\剖析 JSON、該如何存取其內的資料。接著就要說明物件導向 JavaScript(OOJS)。

參見