第 5 章

msg、payload、topic、物件與資料型別

每條 wire 傳遞的是一個 JavaScript 訊息物件 msg;payload 只是其中一個慣用欄位,不是整則訊息。你會用 Debug 逐層確認資料形狀,安全地讀寫巢狀屬性,並辨認複製、解析與 live object 的邊界。

為何需要本章

家庭自動化的錯誤常不是「節點沒執行」,而是上一個節點送出字串 "21.5",下一個節點卻以數字比較;或狀態其實位於 msg.data.new_state.state,流程卻只檢查 msg.payload。先看清訊息,才不會把字串 "false" 當成布林 false,也不會因改動物件而意外影響另一條分支。

本章以 Add-on 22.0.1 內嵌的 Node-RED 5.0.2 為基線。核心 runtime 的 Node.js、訊息 clone 工具與 parser 節點原始碼是行為依據;畫面名稱可能受語系影響,因此操作只使用穩定的節點名與欄位名。情境採用虛構的室內感測資料,不連接真實設備,也不執行 Home Assistant Action。

先記住:msg 是容器,msg.payload 是容器裡的一個值。流程可以沒有 payload,也可以同時帶著 topic、parts、錯誤資訊與自訂欄位。

核心觀念

  • 訊息物件:Node-RED 節點接收並送出 JavaScript object。欄位可包含 primitive、object、array 或 Buffer;各節點只對它文件化的欄位負責。
  • payload:多數核心節點預設處理的主要資料欄位,但不是固定 schema,更不是 msg 的同義詞。
  • topic:慣用的分類或來源標籤。你可以用它區分「客廳溫度」與「書房溫度」,但要由 flow 明確定義其語意,不能假設所有節點都會建立或保留它。
  • _msgid:runtime 在訊息缺少 ID 時產生的追蹤欄位。它方便對照同一路訊息,不應拿來當長期業務鍵、設備 ID 或安全權杖。
  • 結構與型別:結構是欄位路徑與容器關係,型別是值的種類。路徑正確但型別錯誤,條件仍可能不符合。

例如 {"payload":{"temperature":23.4},"topic":"example/room"} 是一個 msg 的簡化視圖:msg.payload 是 object、msg.payload.temperature 是 number、msg.topic 是 string。Debug sidebar 顯示的內容是觀察工具;不要把含住家名稱、位置、事件內容或憑證的完整訊息直接貼到公開 issue。

安全觀察訊息的步驟

  1. 建立無副作用輸入。

    新增 Inject,先以 JSON typed value 輸入 {"temperature":23.4,"humidity":58}。另外把 topic 設為 example/room;這些都是虛構值,不涉及設備。

  2. 先看整個 msg。

    接上 Debug,將輸出屬性選為完整訊息,而非只看 msg.payload。手動觸發一次,展開 sidebar 的 payload,記下各欄位型別與 runtime 加上的 _msgid。

  3. 再縮小到精確路徑。

    複製一個 Debug,只觀察 msg.payload.temperature。確認它顯示 number;若顯示 string,回到來源節點修正 typed value,而不是在每個下游節點猜測轉型。

  4. 加入不破壞來源的映射。

    用 Change 將 msg.payload.temperature 設到 msg.measurement.value,並將字串 °C 設到 msg.measurement.unit。保留原 payload,方便比對前後結構。

  5. 測試缺欄位與錯型別。

    再準備兩個 Inject:一個省略 temperature,一個把它設成字串。只接 Debug 或 Switch,不接 Action;確認你的流程能分辨正常、缺值與型別錯誤。

每次只改一個條件並手動 Inject。這個方式雖慢,卻能建立可重現的訊息契約,之後把虛構輸入替換成 HA 狀態事件時也容易找到差異。

msg 結構、追蹤與複製

Node-RED 5.0.2 runtime 在節點 receive/send 路徑看到訊息缺少 _msgid 時會產生一個。分流時 runtime 建立 send events,第一個送出路徑可沿用訊息,之後需要的路徑會 clone;因此你應把 wire 想成「訊息值的傳遞契約」,不要依賴分支執行先後或共享 reference 的巧合。

「把 msg.payload 指派給另一欄」與「建立深層副本」不是相同操作。object/array 是 reference 型值;若後續程式直接改動巢狀內容,淺層別名可能同時看到變化。Node-RED 的 Change 編輯器在設定值時提供 deep copy 選項,核心 clone 工具也會深層複製一般訊息資料。無論使用哪一種,都應在 Debug 比對來源與目的欄位,尤其不要在 Function 裡修改送出後仍打算重用的物件。

// 教學用結構;不是要求你改用 Function
msg.payload = { temperature: 23.4 };
msg.topic = "example/room";
// msg._msgid 交由 runtime 管理
return msg;

若要建立自己的關聯鍵,請使用名稱清楚且來源可控的欄位,例如 msg.correlation,並限制長度與字元;不要覆寫 _msgid 模擬另一則訊息。Debug 搜尋 ID 只適合執行期追蹤,重新注入、Split 或某些節點處理後都不應被當成永久身分。

live object 例外:HTTP In 流程可能帶有 msg.req 與 msg.res。Node-RED 的 cloneMessage 明確避免深層複製這兩個 live objects,而是保留其 reference。不要把它們放入 context、序列化、跨非必要分支傳遞或輸出到 Debug;HTTP 回應生命週期留到第 19 章處理。

payload、topic 與明確的訊息契約

好的 flow 會在入口寫下契約:需要哪些欄位、接受哪些型別、輸出到哪裡。以房間舒適度判斷為例,可以約定輸入為 msg.payload.temperature number、msg.payload.humidity number、msg.topic string;輸出則為 msg.payload 的摘要 object。這比只說「收到 payload」精確許多。

欄位範例型別用途失敗處理
msg.payload.temperaturenumber室內溫度測試值缺值或非有限數值送到檢查分支
msg.payload.humiditynumber相對濕度測試值超出合理測試範圍時不繼續
msg.topicstring資料來源分類未知 topic 走 else
msg._msgidstring短期診斷關聯只觀察,不自行設定

topic 的價值來自一致性。例如 Switch 可依 msg.topic 將不同房間送到各自統計分支,Trigger 也可依 topic 分開計時;若上游時而填 entity ID、時而填顯示名稱,這些行為就會混亂。選擇一種穩定、非敏感且可驗證的分類,並在 Change 節點集中正規化。

不要把 entity、device、area、tag、zone 或 webhook ID 當成無害文字。它們可能透露住家結構;匯出 flow 前改成明確的 example placeholder。憑證、authorization header 與 access token 則完全不可放進任何訊息範例或 Debug。

物件、陣列、缺值與 mutation

節點的 typed property 欄位通常讓你輸入像 payload.temperature 的路徑,並另外選擇來源是 msg、flow 或 global。不要把字面字串 "payload.temperature" 與 msg property 選項混淆。前者是文字,後者才會讀取路徑。

  • object:用命名欄位表達資料,例如 payload.temperature;先確認父層存在且是 object。
  • array:索引從 0 開始;空陣列、缺少索引與元素為 null 是不同狀況。
  • undefined:常表示路徑不存在。它與 JSON 的 null 不同,也未必能被 JSON 序列化成欄位。
  • mutation:直接改寫 msg.payload.temperature 會改變目前訊息。若原始資料也要供後續比對/追溯,先映射到新的明確欄位。

在家庭情境中,建議把原始事件與推導結果分開,例如保留 msg.payload,把計算結果放在 msg.analysis。對陣列做排序、Split 或 Join 前,先檢查最大元素數;不可信輸入若沒有大小上限,可能讓序列節點長時間保存大量訊息。

多分支流程尤其要避免「一條分支先刪欄位,另一條分支剛好還看得到」這類時序假設。把每一條 wire 的輸入看成獨立契約;需要隔離時使用明確映射或 deep copy,並以兩個 Debug 同時驗證。

Typed value 與 Parser 邊界

Inject、Change、Switch 等節點旁的小型別選擇器會決定輸入如何解讀。常見選項包含 string、number、boolean、JSON、timestamp,以及 msg/flow/global property;部分節點另提供 environment 或 JSONata。可用選項依節點而異,以當下編輯器為準。輸入 23 並選 string,結果仍是字串;視覺上相同不代表型別相同。

值型別注意事項
"off"string是 HA 常見狀態文字語意之一,不等於布林 false
falseboolean可直接作布林判斷
0number不要與字串 "0" 混用
{"value":23.4}object(JSON 解析後)先限制大小並驗證欄位
[1,2,3]array進入 Split 前設定元素上限

Node-RED 5.0.2 核心註冊 CSV、HTML、JSON、XML、YAML parser 節點。Parser 的工作是把某種表示轉成另一種資料結構,不代表內容可信,也不代表 schema 已驗證。JSON 節點可在 JSON 字串與 JavaScript value 之間轉換;HTML parser 可能輸出多則帶 msg.parts 的訊息;CSV/XML/YAML 各有自身結構選項。不要把「成功解析」等同「可安全使用」。

處理外部天氣回應時,先限制回應大小,再用對應 parser,接著以 Switch 檢查必要欄位與型別,最後才映射到內部資料結構。HTML selector、CSV 欄位或 YAML 結構都可能因來源變動而改變;保留 Catch 路徑並清理錯誤輸出中的內容。

故障排除

  • Switch 明明看到 23 卻不符合數字條件:Debug 展開值並查看型別標記;若是 string,回到 Inject、parser 或 Change 使用 number,而不是放寬成模糊比較。
  • 巢狀欄位顯示 undefined:先將 Debug 切回完整 msg,逐層確認 payload、父物件與欄位拼字;同時測試父層為 null 的輸入。
  • 一條分支修改後另一條結果不穩定:停止依賴分支順序,將來源映射到新欄位或使用 deep copy;以兩個 Debug 比對,不要用延遲「修正」資料競爭。
  • 解析器報錯或輸出結構改變:保存已去識別化的小型測試字串,確認 parser 模式與輸出 property,再以 Catch 接住錯誤。切勿把完整外部回應或 header 公開。
  • Debug 出現 req、res 或授權資料:立即停用完整訊息 Debug,刪除可疑 sidebar/log 匯出;live objects 不要複製或保存,憑證依事故程序輪替而非貼到討論區。

固定來源

本章只以 Node-RED 5.0.2 固定提交查證訊息 runtime、複製與核心 parsers,並以官方使用者文件補充概念:

若其他文件的 parser 選項或訊息行為不同,先核對 Node-RED 版本,再用不含敏感資料的測試訊息確認型別、複製與失敗路徑。

常見問題

下游還要比對原始 payload 時,應直接覆寫它嗎?
不要先破壞唯一的原始值。把驗證後的結果映射到用途明確的新欄位,再用兩個受限 Debug 比對;若值是 object 或 array,還要選擇明確的 deep-copy 方法,不能把單純指派 reference 誤認為獨立副本。
可以自行指定 _msgid 當設備識別碼嗎?
不建議。它由 runtime 用於訊息追蹤,不是永久、跨重啟或跨系統的身分契約。請建立用途清楚的自訂欄位,且不要在公開範例放真實設備 ID。
來源有時送 number、有時送 numeric string,能直接比較嗎?
不要直接依賴隱式轉型。先在資料邊界驗證允許格式,明確轉成 number,拒絕空字串、NaN 與超界值,再讓下游只處理單一型別。
複製 msg 後就能安全保存 req/res 嗎?
不能。Node-RED 的 clone 工具對 msg.req 與 msg.res 保留 live reference,並非一般可序列化資料。不要放進 context 或 Debug;在 HTTP flow 的生命週期內最小化使用。
JSON 成功解析是否代表資料安全?
不代表。解析只證明表示法可轉換;你仍須限制大小、檢查 schema、型別與允許值,並為 parser 錯誤準備 Catch 路徑。