空值用 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
}
二、配置合并。
# 默认配置
logLevel: info
# 用户配置(省略)
# 结果:info
# 用户配置(显式 null)
logLevel: null
# 结果:关闭日志输出
这层语义在 Helm、Kubernetes 的 values 合并里非常常见:省略表示「继承默认」,null 表示「显式禁用」。
三、前端表单回填。
输入框拿到 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 判断。列表接口里有些对象带某字段、有些不带,会显著增加消费方出错的概率。
相关工具
- JSON 格式化与校验:对比两份响应体时,
null与缺失的差异会直接体现出来 - JSON 文件的 Git 冲突:union 为什么危险:合并策略对空值的处理