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。
安全觀察訊息的步驟
- 建立無副作用輸入。
新增 Inject,先以 JSON typed value 輸入
{"temperature":23.4,"humidity":58}。另外把topic設為example/room;這些都是虛構值,不涉及設備。 - 先看整個
msg。接上 Debug,將輸出屬性選為完整訊息,而非只看
msg.payload。手動觸發一次,展開 sidebar 的payload,記下各欄位型別與 runtime 加上的_msgid。 - 再縮小到精確路徑。
複製一個 Debug,只觀察
msg.payload.temperature。確認它顯示 number;若顯示 string,回到來源節點修正 typed value,而不是在每個下游節點猜測轉型。 - 加入不破壞來源的映射。
用 Change 將
msg.payload.temperature設到msg.measurement.value,並將字串°C設到msg.measurement.unit。保留原payload,方便比對前後結構。 - 測試缺欄位與錯型別。
再準備兩個 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 或某些節點處理後都不應被當成永久身分。
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.temperature | number | 室內溫度測試值 | 缺值或非有限數值送到檢查分支 |
msg.payload.humidity | number | 相對濕度測試值 | 超出合理測試範圍時不繼續 |
msg.topic | string | 資料來源分類 | 未知 topic 走 else |
msg._msgid | string | 短期診斷關聯 | 只觀察,不自行設定 |
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 |
false | boolean | 可直接作布林判斷 |
0 | number | 不要與字串 "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,並以官方使用者文件補充概念:
- Node-RED 5.0.2 固定提交 61bd08d:
@node-red/runtime/lib/nodes/Node.js、@node-red/util/lib/util.js、@node-red/nodes/core/parsers。 - Node-RED 官方文件:Working with messages。
- Node-RED 官方文件:Understanding message structure。
若其他文件的 parser 選項或訊息行為不同,先核對 Node-RED 版本,再用不含敏感資料的測試訊息確認型別、複製與失敗路徑。
常見問題
下游還要比對原始 payload 時,應直接覆寫它嗎?
可以自行指定 _msgid 當設備識別碼嗎?
來源有時送 number、有時送 numeric string,能直接比較嗎?
NaN 與超界值,再讓下游只處理單一型別。複製 msg 後就能安全保存 req/res 嗎?
msg.req 與 msg.res 保留 live reference,並非一般可序列化資料。不要放進 context 或 Debug;在 HTTP flow 的生命週期內最小化使用。