JAVASCRIPT V2 · EVERYDAY · 60
JSON / localStorage:把 Memory State 轉成可保存的 Data Contract
JavaScript object 活在目前 runtime memory;reload、關頁或換裝置後不會自動存在。要保存或傳輸,必須先 serialization。這一課把 JSON、localStorage、schema/version、parse failure 與 trust boundary 放在同一個完整資料生命週期裡。
Learning outcomes
- 區分 JavaScript value 與 JSON text。
- 理解 stringify/parse round trip 的語意損失。
- 能設計 localStorage 的 load/save/error/version contract。
- 能處理 malformed JSON 與 schema mismatch。
- 理解 client storage 不是 database,也不是 secret vault。
1. Why now:Memory state 不會因為「看起來存在」就永久保存
const students = [
{ name: "Amy", score: 92 }
];
這份 array 在 page runtime 裡存在。Reload 後 JavaScript 重新啟動,必須從某個 persistent representation 恢復。
2. JavaScript object ≠ JSON text
const student = {
name: "Amy",
score: 92
};
const raw =
JSON.stringify(student);
console.log(typeof raw); // string
3. Worked Example A:Round trip
const source = {
name: "Amy",
score: 92
};
const raw =
JSON.stringify(source);
const restored =
JSON.parse(raw);
restored 是新的 JavaScript object,不是 source 的同一 reference。
4. JSON 支援的資料模型有限
JSON 很適合 object/array/string/number/boolean/null,但 JavaScript 的 function、undefined、Date instance 等不會完整保留原語意。
const payload = {
createdAt: new Date(),
helper() {}
};
const raw =
JSON.stringify(payload);
Date 會變成字串 representation;function 不會以可執行 function 的形式被保存。
5. localStorage 只直接保存 string
localStorage.setItem(
"gradeTracker",
JSON.stringify(students)
);
const raw =
localStorage.getItem("gradeTracker");
getItem 找不到 key 時回 null。不要直接把 null 丟進假設有資料的流程。
6. Worked Example B:Safe load
function loadStudents(raw) {
if (raw === null) return [];
try {
const value = JSON.parse(raw);
return Array.isArray(value)
? value
: [];
} catch {
return [];
}
}
這裡把 parse failure 轉成產品選擇:回空資料而不是整頁 crash。不同產品也可以選擇顯示 repair/error UI。
7. Schema validity ≠ JSON syntax validity
JSON.parse('{"version":1}')
// syntax valid
// 但如果 app 需要 students array,
// schema 仍不符合。
成功 parse 只代表 representation 合法,不能證明資料符合你的 domain contract。
8. Versioning:Persistent data 也會有 migration 問題
const payload = {
version: 1,
students
};
之後 schema 改變時,version 讓 loader 有機會辨認舊資料並 migration,而不是猜。
9. Worked Example C:Versioned decoder
function decodePayload(raw) {
if (raw === null) {
return { version: 1, students: [] };
}
try {
const payload = JSON.parse(raw);
if (
payload.version !== 1
|| !Array.isArray(payload.students)
) {
return { version: 1, students: [] };
}
return payload;
} catch {
return { version: 1, students: [] };
}
}10. localStorage scope:它不是 server database
- 同 origin Browser storage。
- 通常不跨裝置同步。
- 使用者/程式可以修改。
- 沒有 relational constraint、transaction、server authorization。
所以「存在 localStorage」不能拿來證明後端資料已保存。
11. Security boundary:不要把 privileged secret 當一般 client state
同 origin JavaScript 可以存取 localStorage,因此它不是「對 Browser JavaScript 保密」的地方。Token 是否適合保存也要看具體 threat model;高權限 server secret 不應放進 client storage。
12. Common mistakes
A. 把 object 直接 setItem
localStorage 會把 value 當 string;應明確 serialize。
B. parse 成功就相信 schema
還要檢查 version/shape。
C. 以為 Date round trip 後仍是 Date instance
通常要重新 parse/construct domain type。
D. broken JSON 讓整個 app 啟動 crash
Persistent boundary 必須有 failure strategy。
13. Debug evidence:把 raw 與 parsed 分開看
console.log({
raw,
rawType: typeof raw
});
const parsed = JSON.parse(raw);
console.log({
parsed,
isArray: Array.isArray(parsed)
});
不要把「storage 有字串」直接等同「domain object 正確」。
14. Guided exercise:Round trip 後哪些東西改變?
const value = {
createdAt: new Date("2026-01-01"),
score: 90
};
const restored =
JSON.parse(
JSON.stringify(value)
);
判斷 restored.createdAt 的 type 與語意。
15. Independent exercise:實作 Versioned Decoder
完成 readStudentCount(raw):null、broken JSON、wrong version、students 非 array 都回 0;合法 v1 payload 回 students.length。
16. Challenge:設計 version 1 → version 2 migration
v1 student 只有 name/score;v2 新增 id。你會在 load 時 migration,還是另做 migration step?需要哪些 deterministic rules 才能安全產生 id?
Project checkpoint:Grade Tracker Persistence Contract
const storageKey =
"gradeTrackerV1";
function saveStudents(students) {
const payload = {
version: 1,
students
};
localStorage.setItem(
storageKey,
JSON.stringify(payload)
);
}
現在 persistence 有明確 representation/version,而不是隨便把 memory object 丟進 storage。
17. Vocabulary / Summary
- serialization:value → 可保存/傳輸 representation。
- deserialization:representation → runtime value。
- schema:資料預期 shape/contract。
- versioning:辨認 persistent format 世代。
- migration:把舊 schema 轉成新 schema。
18. Further reading
Knowledge check
- 區分 JS object 與 JSON text。
- 說明 parse success 為何不代表 schema valid。
- 解釋 Date round trip 的語意損失。
- 說明 localStorage 與 server database 的 boundary 差異。
- 完成 versioned decoder mastery tests。