使用 JSON 資料
JavaScript Object Notation (JSON) 為將結構化資料 (structured data) 呈現為 JavaScript 物件的標準格式,常用於網站上的資料呈現、傳輸 (例如將資料從伺服器送至用戶端,以利顯示網頁)。你應該會常常遇到,因此本文將說明 JavaScript 搭配 JSON 時所應知道的觀念,包含如何在 JSON 物件中存取資料項目,並寫出你自己的 JSON。
必要條件: | 基礎的計算機素養、了解 HTML 與 CSS 的基本概念、熟悉 JavaScript (參閱〈First steps〉與〈Building blocks〉) 與 OOJS 基本概念 (參閱〈Introduction to objects〉)。 |
---|---|
主旨: | 了解應如何使用 JSON 格式所儲存的資料,建立自己的 JSON 物件。 |
說真的,到底什麼是 JSON?
JSON 是依照 JavaScript 物件語法的資料格式,經 Douglas Crockford 推廣普及。雖然 JSON 是以 JavaScript 語法為基礎,但可獨立使用,且許多程式設計環境亦可讀取 (剖析) 並產生 JSON。
JSON 可能是物件或字串。當你想從 JSON 中讀取資料時,JSON 可作為物件;當要跨網路傳送 JSON 時,就會是字串。這不是什麼大問題 — JavaScript 提供全域 JSON 物件,其內的函式可進行切換。
JSON 物件可儲存於其自有的檔案中,基本上就是副檔名為 .json
的文字檔案,以及 application/json
的 MIME type。
JSON 的架構
我們剛提到「JSON 物件基本上就是 JavaScript 物件」,而這敘述在大多數情況下都對。如同標準的 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"
]
}
]
}
舉例來說,如果將此物件載入至 JavaScript 程式並將之儲存為「superHeroes
」變數,如同〈JavaScript 物件基本概念〉一文中提過的,接著能以相同的 存取其內部的資料,如下:
superHeroes.hometown;
superHeroes["active"];
若要順著繼承往下存取資料,只要將必要的屬性名稱與陣列索引「鍊」在一起即可。舉例來說,如果要存取成員列表中的第二位英雄的第三項超能力,你必須:
superHeroes["members"][1]["powers"][2];
- 首先要有變數名稱 —
superHeroes
。 - 要在變數中存取
members
屬性,所以用["members"]
。 members
包含由物件產生陣列。我們要存取陣列中的第二個物件,所以用[1]
。- 在此物件中,我們要存取
powers
屬性,所以用["powers"]
。 - 在
powers
屬性中有 1 個陣列具備所選超級英雄的能力。我們要選第三種能力,所以用[2]
。
备注: 我們在 JSONText.html 範例 (參閱原始碼) 的變數中,示範上述可用的 JSON。你可在自己瀏覽器的 JavaScript 主控台載入此程式碼,並存取變數中的資料。
陣列作為 JSON
我們在上面提過「 JSON 物件基本上就是 JavaScript 物件,而這敘述在大多數情況下都對」。其中「在大多數情況下都對」的理由,就是因為陣列也可以是有效的 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.html 與 style.css 到你的本端硬碟中。後者包含某些簡易的 CSS 可塑造網頁風格;前者則提供極簡單主體 HTML:
<header></header>
<section></section>
加上 <script>
元素,才能納入稍後會在此習題中寫出來的 JavaScript 程式碼。目前只有 2 行程式碼,用以取得 <header>
與 <section>
元素的參考,並將之儲存於變數之中:
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 片段) 來檢索伺幅器的資源,這也代表我們不需載入整個頁面,就能更新小部分的內容。如此可讓網頁反應速度更快;聽起來很棒吧?但可惜本文無法再深入講解更多細節。
-
一開始,我們先針對要在變數中檢索的 JSON 檔案,將其網址儲存起來。把下列程式碼加到你 JavaScript 程式碼的最下方:
jsvar requestURL = "https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json";
-
為了建立請求,我們必須透過
new
關鍵字,先從XMLHttpRequest
建構子建立新的請求物件實例。把下列加到最後一行:jsvar request = new XMLHttpRequest();
-
現在用
open()
函式開啟新的請求。加入下列程式碼:jsrequest.open("GET", requestURL);
這樣就顧到至少 2 個參數。當然也有其他參數可選擇。但這個簡易範例只需要 2 個強制參數:
- 在設立網路請求時,應使用 HTTP 函式。因為這裡只要檢索簡單的資料,所以用
GET
就可以。 - URL 提供請求目的地 — 這也就是我們剛剛儲存的 JSON 檔案網址。
- 在設立網路請求時,應使用 HTTP 函式。因為這裡只要檢索簡單的資料,所以用
-
接著加入下面 2 行程式碼。我們為 JSON 設定了
responseType
,告知伺服器應回傳 JSON 物件,再以send()
函式傳送請求:jsrequest.responseType = "json"; request.send();
-
最後就是等待由伺服器所回傳的反應,再接著處理。把下列程式碼加入現有程式碼的最下方:
jsrequest.onload = function () { var superHeroes = request.response; populateHeader(superHeroes); showHeroes(superHeroes); };
在這裡,我們將所獲得的響應 (可到 response
屬性中找到) 儲存到 superHeroes
變數之中。此變數現在會納入我們的 JSON。接著再把此 JSON 檔案送到 2 個函式呼叫。第一個函式呼叫會將正確資料填入 <header>
;第二個函式呼叫則會為團隊中的各個英文建立資訊卡,再插入至 <section>
內。
當於請求物件上觸發載入事件時,會執行一個事件處理器。我們就將程式碼包裹至此處理器之中 (參閱 onload
) — 只要成功回傳響應,就會觸發載入事件。之所以這樣做,是為了確保當我們要以 request.response
進行某件事時,此 request.response
絕對可用。
產生標頭
現在檢索過了 JSON 資料,接著就寫出上面參照過的 2 個函式來利用 JSON 資料吧。首先將下列函式定義加到先前的程式碼中:
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>
元素、將其 textContent
指定為 JSON 的 squadName
屬性、透過 appendChild()
將之附加到標頭。接著 <p>
元素依樣畫葫蘆一遍:建立、設定其文字內容、附加到標頭。唯一不同之處,就是將該文字設定為 1 組串接字串 (Concatenated string),其內包含 JSON 的 homeTown
與 formed
屬性。
建立超級英雄的資訊卡片
現在將下列函式加到程式碼底端,用以建立並顯示超級英雄的卡片:
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 組
<article>
、1 組<h2>
、3 組<p>
、1 組<ul>
。 - 讓
<h2>
納入目前超級英雄的name
。 - 接著 3 個段落分別是英雄的
secretIdentity
、age
、Superpowers
,在列表中帶出相關資訊。 - 另以新變數
superPowers
儲存powers
屬性 — 其中包含 1 組陣列以列出目前英雄的超能力。 - 再用另一個
for
迴圈逐一巡過目前英雄的超能力。針對每一項超能力,我們再建立 1 組<li>
元素,把超能力放進該元素之中,再透過appendChild()
把listItem
放入<ul>
元素之內 (myList
)。 - 最後就是在
<article>
(myArticle
) 之內附加<h2>
、<p>
、<ul>
;再把<article>
附加於<section>
之內。這附加的順序極為重要,因為這也會是 HTML 中的顯示順序。
备注: 如果你無法讓此範例運作,可參閱我們的 heroes-finished.html 原始碼 (亦可看到實際執行情況。)
备注: 如果你無法用我們說過的點記法 (dot-)\括弧記法 (bracket notation) 來存取 JSON,則可用新分頁或自己的文字編輯器開啟 superheroes.json 檔案並參考之。你也可再回去看看 JavaScript 物件基礎概念 ,再次了解點\括弧記法。
物件與文字交互轉換
上述是存取 JSON 的簡易範例,因為我們設定要回傳響應的 XHR 已經是 JSON 格式。透過:
request.responseType = "json";
但有時候沒這麼好運。我們有時會接收到文字字串格式的 JSON 資料,且必須將之轉換為物件。且當我們要以某種訊息傳送 JSON 資料時,也必須將之轉換為字串才能正確運作。還好,這 2 種問題在 Web 開發過程中甚為常見。內建的 JSON 物件很早就新增到瀏覽器之中,且包含下列 2 種函式:
parse()
:接收文字字串形式的 JSON 物件作為參數,並回傳對應的物件。stringify()
:接收 JSON 物件作為參數,並回傳對等的文字字串形式。
你可到 heroes-finished-json-parse.html 範例 (參閱原始碼) 中看到第一個函式的運作情形。這其實跟我們先前範例所進行的事情一模一樣,不同之處在於我們設定 XHR 要回傳 JSON 為文字,接著再使用 parse()
轉換為實際的 JSON 物件。關鍵程式碼片段如下:
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 主控台上輸入下列程式碼,看看其運作方式:
var myJSON = { name: "Chris", age: "38" };
myJSON;
var myString = JSON.stringify(myJSON);
myString;
這樣就建立了 JSON 物件了。接著檢查內容物之後,就可透過 stringify()
將之轉換為字串。將回傳值儲存到新變數之中,再檢查一次即可。
摘要
我們透過本文簡單介紹了該如何在程式中使用 JSON、該如何建立\剖析 JSON、該如何存取其內的資料。接著就要說明物件導向 JavaScript(OOJS)。