Learning Objectives
- 說明 JSON 的用途與語法規則,分辨 JSON 字串與 JavaScript 物件字面值
- 用 JSON.parse 把 JSON 字串轉成物件,並用 try…catch 處理格式錯誤
- 用 JSON.stringify 把物件轉成 JSON 字串,說明 undefined、函式、Date 會怎麼處理
- 分辨指定、淺複製與深複製,用展開、Object.assign、JSON 往返與 structuredClone 複製資料
JSON:交換資料用的文字格式
JSON(JavaScript Object Notation,JavaScript 物件表示法)是一種用文字表示資料的格式。 網頁向伺服器要資料、程式儲存設定檔、不同程式語言之間交換資料,最常用的就是 JSON。 它的寫法源自 JavaScript 的物件與陣列,但現在幾乎所有程式語言都有讀寫 JSON 的函式庫。
{
"course": "JavaScript",
"hours": 30,
"online": true,
"teacher": { "name": "Amy" },
"tags": ["web", "frontend"],
"room": null
}
- 物件用
{ },裡面是「鍵: 值」,用逗號分隔;陣列用[ ] - 整份 JSON 可以是一個物件,也可以是一個陣列(或單一的值)
- 取資料的方式和 JavaScript 一樣:轉成物件之後,
data.teacher.name、data.tags[0]
JSON 比物件字面值嚴格
JSON 長得很像 JavaScript 的物件字面值,但規則嚴格許多:
| 規則 | JSON | JavaScript 物件字面值 |
|---|---|---|
| 鍵 | 一定要用雙引號:"name" |
可以不加引號:name |
| 字串 | 一定用雙引號 "…" |
單引號、雙引號、反引號都可以 |
| 最後一個元素後面的逗號 | 不可以 | 可以 |
| 註解 | 不可以 | 可以 |
| 值的型別 | 字串、數字、true/false、null、物件、陣列 |
任何值,包括函式、undefined、Date |
兩者的身分也不同:JSON 是一段字串(typeof 得到 'string'),要先用 JSON.parse 轉成物件才能取屬性;
物件字面值寫在程式裡,本身就是物件。
| 方法 | 方向 |
|---|---|
JSON.parse(text) |
JSON 字串 → JavaScript 的值 |
JSON.stringify(value) |
JavaScript 的值 → JSON 字串 |
JSON.parse:字串變成物件
text 只是一段字串,右邊顯示它的值是一長串文字。第 6 行 JSON.parse 之後,heroes 變成真正的陣列,裡面有兩個物件,可以一層一層取值。
JSON.stringify:哪些東西會不見
JSON.stringify 把值轉成 JSON 字串,遇到 JSON 沒有的型別時會這樣處理:
| 值 | 在物件的屬性裡 | 在陣列裡 |
|---|---|---|
undefined、函式、Symbol |
整個屬性被省略 | 變成 null |
NaN、Infinity |
變成 null |
變成 null |
Date |
變成 ISO 格式的字串(例如 "2026-10-29T00:00:00.000Z",以 UTC 表示) |
同左 |
所以 JSON.parse(JSON.stringify(x)) 之後,日期會變成字串、方法會消失。
JSON.stringify(value, replacer, space) 還有兩個選擇性參數:
space:縮排,數字代表幾個空白(最多 10),讓輸出比較好讀,例如JSON.stringify(data, null, 2)replacer:一個函式或屬性名稱的陣列,用來決定哪些屬性要輸出、要怎麼轉換
JSON.parse(text, reviver) 也可以給一個 reviver 函式,在解析時轉換每一個值。
stringify 的規則
data 裡有 undefined、一個方法、一個 Date 和含有 undefined、NaN 的陣列。看第 15 行印出的字串少了什麼、變了什麼。
格式錯誤時,JSON.parse 會丟出錯誤
五段字串只有兩段是合法的 JSON。單引號、鍵沒有引號、最後多一個逗號,在 JavaScript 物件字面值都可以,在 JSON 都不行。
想一想:JSON 的規則
下面的程式會印出什麼?
const s = JSON.stringify({ a: 1, b: undefined, c: [undefined] });
console.log(s);
下列哪一段是合法的 JSON?
下面的程式會印出什麼?
const text = '{"n": 5}';
console.log(typeof text, typeof JSON.parse(text).n);
指定、淺複製與深複製
第 8、9 課提過:物件與陣列是參照。要「複製」時,有三種程度:
| 做法 | 例子 | 外層是新的嗎 | 內層的物件 |
|---|---|---|---|
| 指定 | const b = a |
不是,同一個 | 同一個 |
| 淺複製(shallow copy) | { ...a }、Object.assign({}, a)、[...arr]、arr.slice() |
是 | 還是同一個 |
| 深複製(deep copy) | structuredClone(a)、JSON.parse(JSON.stringify(a)) |
是 | 也是新的 |
基本型別(數字、字串、布林值)沒有這個問題:let num2 = num1 就是複製值,之後改 num1 不影響 num2。
淺複製只複製第一層的屬性。如果屬性值是物件或陣列,新舊兩份共用同一個內層物件,透過任何一邊修改內層,另一邊也會看到。 資料只有一層、或內層不會被修改時,淺複製就夠用了。
淺複製:內層物件是共用的
播放到第 12 行時看右邊的箭頭:same 和 original 指向同一個物件;shallow、assigned 是新的物件,但它們的 inner 都指向同一個 {b: 20}。
深複製的兩種作法
JSON 往返:JSON.parse(JSON.stringify(a)) 先轉成字串再轉回來,得到的每一層都是新的。寫法簡單,但受 JSON 規則限制:
函式與 undefined 會不見、Date 會變成字串、NaN 會變成 null。
structuredClone:瀏覽器與 Node.js 內建的深複製函式,structuredClone(a)。它能正確複製 Date、Map、Set 等型別,
也能處理物件互相參照的情況;但不能複製函式(以及網頁元素等),遇到時會丟出 DataCloneError。
主要瀏覽器從 2022 年起都已支援,現在建議優先使用它。
投影片示範的另一種作法是用迴圈把每個屬性一一複製到新物件。只複製一層就是淺複製;要深複製,遇到內層物件時要再對它重複同樣的步驟。
深複製:JSON 往返與 structuredClone
第 6、7 行修改了原本的 a,但兩份深複製都不受影響:右邊的 viaJson、viaClone 各自有自己的內層物件。第 14、15 行比較兩種作法處理 Date 的差別。
想一想:複製
下面的程式會印出什麼?
const a = { x: 1, inner: { y: 1 } };
const b = { ...a };
b.x = 2;
b.inner.y = 2;
console.log(a.x, a.inner.y);
下面的程式會印出什麼?
const a = { list: [1, 2] };
const b = JSON.parse(JSON.stringify(a));
b.list.push(3);
console.log(a.list.length, b.list === a.list);
要深複製 { when: new Date(), tags: ['a'] },而且複製後的 when 仍然要是 Date,用哪一個?
實作:解析 JSON 並轉換欄位
text 是一段 JSON,sex 用 1、0 表示男、女,married 用 true、false 表示已婚、未婚。請用 JSON.parse 解析,再逐筆印出「姓名,性別,婚姻狀態,城市」。
重點精華回顧
- JSON 是交換資料用的文字格式:物件用
{ }、陣列用[ ],鍵與字串一律雙引號,不能有結尾逗號與註解。 JSON.parse把 JSON 字串轉成 JavaScript 的值;格式錯誤會丟出SyntaxError,要用try…catch處理。JSON.stringify把值轉成 JSON 字串:undefined與函式被省略(在陣列裡變成null)、Date變成 ISO 字串;第三個引數可以縮排。- 指定(
b = a)不會複製;淺複製(展開、Object.assign、slice)只複製第一層,內層物件仍共用。 - 深複製連內層都複製:
structuredClone保留Date等型別但不能複製函式;JSON 往返簡單,但會失去函式並把日期變成字串。