核心概念
本頁說明 NanoJSON 如何將一份 JSON 文件建模為可即時編輯的 DOM 樹,以及型別系統的實際行為(含邊界情況)。
JSONEditor:根控制器
JSONEditor(src/model/JSONEditor.js)是唯一的公開入口。建構時會:
- 依
config.css注入樣式表(loadCSS) - 建立
Lifecycle實例並掛上when.*回呼 - 透過
getJSON()載入初始資料(json/file/path) - 以
#jsonToChildren()將載入的資料遞迴轉換為JSONEditorNode陣列 - 掛載至
config.id指定的既有元素,或建立新的<section class="pd-json-editor">
若載入後 children.length < 1,會自動呼叫一次 insert() 新增一個空節點(JSONEditor.js:153-155)。
JSONEditorNode:遞迴節點
JSONEditorNode(src/model/JSONEditorNode.js)代表樹中的單一鍵值對或陣列項目,持有 key / type / value / children / collapsed。每個節點的 DOM 由 #create() 局部渲染並快取於私有欄位 #dom;新增(#add)與移除(#remove)子節點時,只替換受影響的子樹,不重繪整棵樹。
型別系統與邊界情況
型別由 getType()(src/data.js)依 JavaScript 執行期型別推導:
| JS 值 | getType() 回傳 |
|---|---|
Array.isArray(v) 為真 |
"array" |
typeof v === "object"(含 null) |
"object" |
typeof v === "boolean" |
"boolean" |
typeof v === "number" |
"number" |
| 其他 | "string" |
注意:getType(null) 回傳 "object",因為 JavaScript 的 typeof null === "object"。是否顯示為 null 型別節點,取決於呼叫端如何處理這個回傳值,而兩個呼叫點的處理方式並不一致:
- 物件的子鍵值(
JSONEditor.js:227-231):先判斷value != null才視為巢狀物件/陣列;若value === null,會額外將節點型別覆寫為"null"。 - 陣列的子項目(
JSONEditor.js:205-209):沒有對應的null特殊處理。若陣列元素為null,getType()回傳"object",但因為e != null為假,不會進入巢狀分支,最終落入else分支,以String(e)也就是文字"null"存成一個型別為預設值"string"的節點,而非"null"型別節點。
這代表:{"a": null} 匯入後會渲染為型別下拉選單顯示 null 的節點;而 [null] 匯入後會渲染為一個內容為文字 "null" 的 string 節點。若需要陣列中保留真正的 null 型別,需在應用層自行處理這個差異。
序列化:json getter 與 export() 的差異
JSONEditorNode.json(getter)遞迴呼叫私有的 #json(),依節點型別回傳對應的 JavaScript 值(array 回傳陣列、object 回傳物件、boolean/number 做型別轉換、其餘回傳字串)。
根層級的序列化則有兩種路徑,行為並不完全相同:
| 路徑 | 篩選條件(JSONEditor.js) |
行為 |
|---|---|---|
editor.json(getter,第 267-277 行) |
只收 e.key 為真的子節點 |
沒有鍵的子節點(例如根為陣列或單一純量時的節點)會被忽略 |
editor.export()(第 289-310 行) |
收 e.key 為真,或當 children.length === 1 時也收 |
根層只有一個無鍵子節點時(陣列根、純量根)仍會被寫入輸出檔案 |
也就是說:若根節點目前是一個陣列(type === "array" 且只有一個子節點且該子節點無 key),editor.json 會回傳 "{}",但 editor.export() 卻能正確下載出該筆資料。呼叫 editor.json 前,若根型別可能是陣列,需注意這個差異。
唯讀模式
readonly 設定不是逐節點傳遞的旗標,而是在初始化完成後,透過 document.querySelectorAll(selectorSubNodes)(button:not(.collapsed), textarea, input, select)對整棵已渲染的 DOM 樹批次加上 / 移除 disabled 屬性(見 enable() / disable(),JSONEditor.js:171-183)。切換唯讀狀態不會觸發 Lifecycle 的任何鉤子。