空值用 null 還是省略欄位
結論:`null` 表示「這個欄位存在,但沒有值」,省略表示「這個欄位不存在」。PATCH 介面、設定合併、前端表單回填三處最容易因此出錯,判斷標準是「消費方需不需要知道這個欄位被顯式清空」。

結論:null 表示欄位存在但值為空,省略表示欄位不存在,選錯會直接改變業務含義;PATCH 請求必須用 null 表示清空、省略表示不修改,回傳體與設定合併則各自統一約定。
語意差異
{ "nickname": null }
欄位存在,值是空。消費方知道「這個欄位被顯式清空過」。
{ }
欄位不存在。消費方無法判斷是「沒設定」還是「不支援」。
在 JavaScript 裡這個差異非常具體:
'nickname' in obj // false —— 欄位不存在
obj.nickname === null // true —— 欄位存在且為 null
obj.nickname === undefined // true —— 兩種情況都成立
三處必須區分的地方
一、PATCH 請求。
PATCH /users/1
{ "nickname": null }
含義是「把暱稱清空」。
PATCH /users/1
{ }
含義是「什麼都不改」。如果 PATCH 實作裡把「值為 null」和「欄位缺失」當成一回事,使用者就永遠沒法清空暱稱——只能改成空字串,而空字串和 null 在業務上往往是兩回事。
正確實作要用「欄位是否存在」判斷,而不是「值是否為 null」:
if (Object.hasOwn(patch, 'nickname')) {
target.nickname = patch.nickname; // 允許寫成 null
}
二、設定合併。 省略表示「繼承預設」,null 表示「顯式停用」。這層語意在 Helm、Kubernetes 的 values 合併裡非常常見。
三、前端表單回填。 輸入框拿到 null 應該顯示為空,拿到 undefined(欄位不存在)通常也顯示為空,但提交時要區分:原本有值、現在清空 → 提交 null;從未填過 → 不提交這個欄位。不區分會導致「清空操作被當成未修改」。
常規建議
| 場景 | 建議 |
|---|---|
| 查詢介面的回傳體 | 保留 null,保持結構穩定,前端型別好寫 |
| PATCH 請求體 | 嚴格區分:null 清空,省略不改 |
| PUT 全量替換 | 可以省略,但語意是「整份覆蓋」,缺失欄位視為刪除 |
| 設定合併 | 省略 = 用預設值,null = 顯式關閉 |
| 資料庫儲存 | null 表示未知/不適用;空字串表示「已知為空」 |
回傳體盡量保持結構穩定是重要的一條。列表介面裡有些物件帶 avatar、有些不帶,前端每次都要 'avatar' in item 判斷,很容易出錯。統一回 avatar: null 更好寫。
別做的三件事
別用空字串代替 null。 "" 和 null 在業務上經常不同:暱稱為空串是「使用者設定了空暱稱」,null 是「沒設定」。
別用 0 或 -1 表示「沒有值」。 數值欄位裡 0 是合法值,當哨兵值用會讓統計邏輯出錯。
別在同一個欄位上混用兩種約定。 有的介面回 null,有的省略欄位,前端就得寫兩套判斷。約定一次,寫進文件。
常見問題
什麼時候必須用 null 而不是省略欄位?
消費方需要區分「欄位被顯式清空」和「欄位從未修改」時必須用 null。典型場景是 PATCH 請求:傳 null 表示清空該欄位,不傳表示保持原值。
PATCH 介面如何區分「清空」和「不修改」?
用「欄位是否存在」判斷,而不是「值是否為 null」。例如用 Object.hasOwn(patch, 'nickname') 判斷後再賦值,這樣 null 才能作為合法值生效。
為什麼回傳體應該盡量保留 null?
保留 null 能讓回傳結構穩定,前端不必對每個可選欄位寫 'avatar' in item 判斷。列表介面裡有些物件帶某欄位、有些不帶,會顯著增加消費方出錯的機率。