JSON 文档合并的两种语义
把两个 JSON 文档合起来有两种截然不同的语义:一种是按补丁规则覆盖,空值表示删除字段,嵌套对象递归合并;另一种是保留式合并,数组拼接、重复键都留下。它们对同一组输入会给出完全不同的结果,用哪一个取决于业务意图——配置更新走覆盖,标签收集走保留。PostgreSQL 侧只有并列运算符,做的是顶层合并,右侧同名字段直接覆盖,嵌套对象整体替换。
基本信息
| 适用平台 | MySQL 8.0 / PostgreSQL 12 及以上 |
|---|---|
| MySQL 覆盖式 | 按补丁语义:空值删键、嵌套对象递归合并、非对象直接替换 |
| MySQL 保留式 | 数组拼接、重复键都保留,是旧函数的等价物 |
| PostgreSQL 写法 | 并列运算符,顶层合并,右侧覆盖同名键 |
| 版本提示 | 旧的合并函数自 8.0.3 起废弃,需按语义替换 |
典型使用场景
- 配置增量更新:只提交变化的字段,其余保持原值
- 标签或数组收集:多次追加同一字段的值
- 接口参数覆盖:默认配置与请求参数合并
- 排查数据差异:确认某次合并到底是覆盖还是追加
- 从旧版函数迁移:按当前语义选择覆盖或保留
两种语义的结果差异
两种语义的结果差异
-- MySQL 覆盖式:空值删除键,嵌套对象递归合并
SELECT JSON_MERGE_PATCH('{"a":1,"b":2}', '{"a":null,"c":3}');
-- 结果:{"b": 2, "c": 3} ← a 被删除
-- MySQL 保留式:数组拼接,重复键都保留
SELECT JSON_MERGE_PRESERVE('{"a":[1,2]}', '{"a":[3]}');
-- 结果:{"a": [1, 2, 3]}
-- PostgreSQL:并列运算符做顶层合并,右侧覆盖同名键
SELECT '{"a":1,"b":2}'::jsonb || '{"a":9,"c":3}'::jsonb;
-- 结果:{"a": 9, "b": 2, "c": 3}
-- 注意:嵌套对象是整体替换,不会递归合并
SELECT '{"cfg":{"x":1,"y":2}}'::jsonb || '{"cfg":{"x":9}}'::jsonb;
-- 结果中 cfg 只剩 x,y 消失语义对照
| 输入情况 | 覆盖式合并 | 保留式合并 |
|---|---|---|
| 同名字段 | 右侧覆盖左侧 | 两个值都保留 |
| 空值字段 | 删除该键 | 保留为空值 |
| 嵌套对象 | 递归合并 | 递归合并 |
| 数组字段 | 右侧替换左侧 | 数组拼接 |
| 非对象值 | 右侧直接替换 | 按保留规则组合 |
把覆盖式合并当「深度更新」用要格外小心:嵌套对象确实会递归合并,但显式写空值表示删除整个键。提交参数前要确认空值的含义是「删除」还是「不修改」,两者差一个字段就会丢配置。
PostgreSQL 的并列运算符是顶层合并,嵌套对象整体替换。需要递归语义时得在应用层处理,或用支持路径更新的函数逐个字段改写。