NotesJSONArray / Element / Object:LotusScript parse + build JSON 的三個組成元件
重點摘要
- 接續之前發佈過的 lotusscript-http-json — 把
NotesJSONNavigator底下的 3 個組成元件講透 - 3 個 class 對應 JSON 的 3 種 node:
NotesJSONElement— 葉節點(name/value pair)NotesJSONObject— 物件{}節點NotesJSONArray— 陣列[]節點
- 每個都同時支援 parse + build:parse 用
GetFirstElement/GetNextElement/GetNthElement走、build 用AppendElement/AppendArray/AppendObject組 - 反向 build JSON 的起手:
session.Createjsonnavigator("")拿空 navigator、append 完用.Stringify()取 JSON 字串 - 64K 限制版本敏感 — parse 端 64K 限制跟「CR/LF 換行字元讀不進」這兩個 都在 10.0.1 FP2 修了(SPR# DCONB8VMAV / ASHEB95LFR);14.5 環境唯一還活著的隱形地雷是「單一 element value > 64K 仍會亂」(LS String 底層限制、不是 class 能繞的)
三個 class 的關係
對應到 JSON 的結構是這樣:
{ ← 整個是一個 Object "name": "Bryan", ← Element:name="name", value="Bryan" "tags": ["Domino", "AI"] ← Element:name="tags", value=Array}| Class | 對應 JSON 結構 | 取值方式 | 子節點 |
|---|---|---|---|
NotesJSONObject | {...} | 依名稱(GetElementByName)或順序(GetNthElement) | 任意混合 Element / Object / Array |
NotesJSONArray | [...] | 依索引(GetNthElement)— 沒有名稱 | 任意混合 Element / Object / Array |
NotesJSONElement | "name": value | 直接讀 .Name / .Value / .Type | 葉節點、無子節點 |
NotesJSONNavigator 是整棵樹的 entry point — 從它拿到 root(通常是 Object 或 Array)後、就走這 3 個 class 的方法走完整棵樹。
NotesJSONElement — 葉節點
最簡單、3 個 property + 1 個 method:
| 成員 | 用途 |
|---|---|
.Name | element 名稱(在 Object 裡才有意義;在 Array 裡是空) |
.Type | 常數、標示 .Value 的型別(string / number / boolean / null / object / array) |
.Value | 值本身 |
.Copy(otherEl) | 把另一個 element 的值複製進來 |
.Type 的常數實際值看 Designer 說明文件的 NotesJSONElement class 條目。正式環境通常用 .Type 先判型再取 .Value:
Dim el As NotesJSONElementSet el = obj.GetElementByName("age")If el.Type = JSON_TYPE_NUMBER Then ' 常數實際名稱看 Designer help Print "Age is " & CStr(el.Value)End IfNotesJSONObject — {} 節點
帶名稱的 key/value 集合。Size + 4 個 navigation + 3 個 append + Copy:
| 成員 | 用途 |
|---|---|
.Size | 元素數量 |
.GetElementByName(name) | 最常用 — 依名稱直取 |
.GetFirstElement() / .GetNextElement() | 走完整個 object(順序未保證 strict、但通常是插入順序) |
.GetNthElement(n) | 依插入順序的索引取(從 1 開始或 0 開始要查 doc 確認) |
.AppendElement(el) | 加一個葉節點 |
.AppendArray(arr) | 加一個 array 子節點 |
.AppendObject(obj) | 加一個 object 子節點 |
.Copy(otherObj) | 整個 object 拷貝 |
注意 AppendElement / AppendArray / AppendObject 三個分開、不是 overload — append 什麼型別呼對應的 method。
NotesJSONArray — [] 節點
跟 Object 幾乎一樣、但沒有 GetElementByName(陣列無名):
| 成員 | 用途 |
|---|---|
.Size | 陣列長度 |
.GetFirstElement() / .GetNextElement() | 走完整個陣列 |
.GetNthElement(n) | 依索引取 |
.AppendElement(el) / .AppendArray(arr) / .AppendObject(obj) | 加元素 / 子陣列 / 子物件 |
.Copy(otherArr) | 整個陣列拷貝 |
JSON 標準允許陣列裡混型別([1, "two", {"three": 3}])— 三個 Append 方法各自獨立就是為了支援這個。
Parse 場景:從 navigator 走進去
之前那篇 lotusscript-http-json示範了「HTTP Get + PreferJSONNavigator = True 直接拿 navigator」。拿到 navigator 後實務上的 3 種走法:
1. 直接抓特定欄位(Object 路徑)
Dim root As NotesJSONObjectSet root = nav.GetElementByName("data").Value ' 假設 data 是 objectDim emailEl As NotesJSONElementSet emailEl = root.GetElementByName("email")Print emailEl.Value2. 走完整個物件
Dim el As NotesJSONElementSet el = obj.GetFirstElement()Do Until el Is Nothing Print el.Name & " = " & CStr(el.Value) Set el = obj.GetNextElement()Loop3. 巡陣列 + 對每個 item 抓欄位
Dim arr As NotesJSONArraySet arr = root.GetElementByName("users").Value ' users: [...]Dim i As IntegerFor i = 1 To arr.Size Dim user As NotesJSONObject Set user = arr.GetNthElement(i).Value Print user.GetElementByName("name").ValueNext iGetElementByPointer 補充:navigator 還有
GetElementByPointer(JSON Pointer RFC 6901 語法)給深層 path 直取 —nav.GetElementByPointer("/data/users/0/email")比一層層 GetElementByName 簡潔。
Build 場景:反向組 JSON
之前那篇沒提的、這 3 個 class 也能反過來組 JSON。起手:
Dim session As New NotesSessionDim nav As NotesJSONNavigator
' 空 object 起手Set nav = session.Createjsonnavigator("")' 或空 array 起手:' Set nav = session.Createjsonnavigator("[]")然後用 Append* 加結構:
Dim root As NotesJSONObjectSet root = nav.GetFirstElement().Value ' 取 root object
' 加葉節點Dim nameEl As NotesJSONElementSet nameEl = nav.AppendElement("Bryan") ' 文字nameEl.Name = "name"Call root.AppendElement(nameEl)
' 加 arrayDim tagsArr As NotesJSONArraySet tagsArr = nav.AppendArray() ' 取空 arrayCall root.AppendArray(tagsArr)' 然後對 tagsArr 用同樣 Append* 加 items
' 序列化回 JSON 字串Dim jsonStr As StringjsonStr = nav.Stringify()實際 API 細節(AppendElement 取的是值還是已建好的 element 物件?index 從 0 還 1?)建議參考 eknori 2019 的完整範例 — 是社群裡最完整的 build 端示範。
完整 round-trip:POST 出去 + parse 回應
把 user input 組成 JSON、POST 到外部 API、parse 回應:
Sub PostAndParse Dim session As New NotesSession Dim http As NotesHTTPRequest Dim navOut As NotesJSONNavigator Dim navIn As NotesJSONNavigator Dim root As NotesJSONObject
' 1. Build request JSON Set navOut = session.Createjsonnavigator("") Set root = navOut.GetFirstElement().Value
Dim el As NotesJSONElement Set el = navOut.AppendElement("hello world") el.Name = "message" Call root.AppendElement(el)
' 2. POST it Set http = session.CreateHTTPRequest() http.PreferJSONNavigator = True http.ContentType = "application/json" Set navIn = http.Post("https://api.example.com/echo", navOut.Stringify())
' 3. Parse response Dim respRoot As NotesJSONObject Set respRoot = navIn.GetFirstElement().Value Dim statusEl As NotesJSONElement Set statusEl = respRoot.GetElementByName("status") Print "API returned status: " & statusEl.ValueEnd Sub完整 HTTP 範例(含 error handling、headers、TLS trust store)在 lotusscript-http-json 跟 notes-httprequest-14-5-trust-store。
64K 限制 — 版本敏感、分層看
這個議題版本演進有點細、值得分層講:
| 版本 | parse 端 64K 行為 |
|---|---|
| 10.0.1 GA 以前 | Createjsonnavigator(string) 字串參數上限 64K、超過必須走 NotesStream overload |
| 10.0.1 FP2 以後 | 整包 JSON > 64K 沒問題(SPR# DCONB8VMAV 修了)、但單一 element value > 64K 仍會亂掉 |
| 任何版本 | NotesStream overload 直接傳 stream 物件最穩 — 不要先 .ReadText() 變字串再傳(會撞回字串 64K 限制) |
對 14.5 環境(多數讀者應該是這條)來說:原本的「parse 端 64K 限制」7 年前就修了、「CR/LF 換行字元讀不進」也同版本順手修了(SPR# ASHEB95LFR)。剩下唯一還活著的隱形地雷是 ↓
⚠️ 單一 element value > 64K — 14.5 還在的隱形地雷
eknori 2019/05/30 follow-up 實測:升 10.0.1 FP2 之後整包 > 64K 沒事、但某個 element 的 value 超過 64K(譬如 JSON 裡塞一坨 base64 編碼的 PDF)、NotesJSONElement 取出來會「strange results」。
根因:LotusScript String 本身的底層限制(跟 NotesItem 的 32K text、64K summary 是同源問題)、不是 JSON class 能繞的。實務上資料結構是「meta + 一坨 base64 附件」的話:
- 附件拆走另一個 HTTP call(multipart 或 binary stream)
- 或拆成分塊陣列(每塊 < 60K)在應用層重組
NotesStream 用法 — 重點是傳 stream 物件、不要 ReadText
Designer help 的 NotesJSONNavigator 條目列了三種 overload:no input / string / NotesStream。NotesStream 版正確用法:
Dim session As New NotesSessionDim stream As NotesStreamDim nav As NotesJSONNavigator
Set stream = session.CreateStreamCall stream.Open("c:\temp\big.json", "UTF-8") ' UTF-8 強制stream.Position = 0Set nav = session.Createjsonnavigator(stream) ' ← 直接餵 stream 物件Call stream.Close ' navigator 建好後 stream 就可以關常見錯誤 — 先 ReadText 變字串再傳:
Set nav = session.Createjsonnavigator(stream.ReadText) ' ❌ 字串 64K 限制照樣撞eknori 那篇文章留言區就有人踩過這個坑:原本 stream.Position = 0; Set jsnav = session.CreateJSONNavigator(stream.ReadText); 一直 error、後來才發現必須直接傳 stream 物件、不是 ReadText 出來的字串。
順帶:stream.Open 第二個參數帶 "UTF-8"、原本對「UTF-8 only」的擔心也順手解(從 stream 進的字串強制就是 UTF-8)。Help 文件另外提一句「The NotesStream must be opened when creating the navigator and can be closed as soon as the navigator is created」、navigator 建好後 stream 隨時 close。
Build / POST 端大 payload — 也是 FP2 解的
navigator.Stringify() 回的 String 本身沒上限(LS String 可達 2GB)、問題會出在下一步 — NotesHTTPRequest.Post(url, body) 在 10.0.1 之前也撞 64K(SPR# JCORBB2KWU、跟 parse 端同版本一起修)。14.5 環境同樣不用擔心。
撞到舊版又要 POST 大 payload — 傳統解法是退回去用 Java agent 或 LS2J 包 Apache HttpClient(V12 之前 NotesHTTPRequest 的 HTTP 實作還相對陽春)。
跟先前 HTTP / JSON 那篇的關係
| 主題 | 哪篇 |
|---|---|
怎麼打 HTTP、PreferJSONNavigator 怎麼設、navigator 是什麼 | lotusscript-http-json |
| 本篇:navigator 下面 3 個組成元件怎麼用 + 反向 build | 你在看的這篇 |
| HTTPS trust store 細節(V14.5+) | notes-httprequest-14-5-trust-store |
同類別在其他語言
| 語言 | 對應 |
|---|---|
| LotusScript | NotesJSONElement / NotesJSONObject / NotesJSONArray |
| Java | JsonJavaArray / JsonJavaObject / JsonJavaFactory(Domino Java API、概念對位) |
| SSJS | 用 JS 原生 JSON.parse / JSON.stringify — XPages 跑 SSJS 本來就有完整 JSON 支援、不需要 Notes 專屬 class |
SSJS 場景反過來:因為 JS 對 JSON 是 first-class、沒人會用 Domino 專屬 class、直接用 native JSON.parse(http.responseText) 就行。LotusScript 之所以有這 3 個 class、是因為 LS 本身沒 JSON literal 語法、需要 class 來表達 JSON 結構。