JSON 合并实战:深度合并两个或多个对象怎么不丢键
讲清 JSON 合并里浅合并和深度合并的真正区别,后者为什么覆盖前者,数组的四种合并策略各适合什么场景,以及配置叠加、默认值加用户设置、补丁落地这些常见用途的具体做法。
JSON 合并实战:深度合并两个或多个对象怎么不丢键
合并两个 JSON 对象听起来是小事,真动手才发现坑都藏在"嵌套"两个字里。把生产配置叠到基底配置上,本想只改数据库地址,结果整个 logging 段不见了。问题几乎都出在一个地方:你以为在做深度合并,实际跑的是浅合并。
浅合并和深度合并到底差在哪
浅合并就是 Object.assign 或展开运算符 {...a, ...b} 干的事:它只看顶层键,遇到同名键直接拿后一个的值整块替换,不管那个值本身是不是对象。
举个最小的例子。{"a":{"x":1}} 浅合并 {"a":{"y":2}},结果是 {"a":{"y":2}},x:1 被整块盖掉、彻底丢了。因为浅合并看到顶层有个 a,两边都有,后者赢,于是后者那个 {"y":2} 原封不动顶替了前者,根本没往里钻。
深度合并的区别就在"往里钻"这一步。它会递归遍历两棵树,碰到同名键如果两边都是对象,就钻进去继续按键合并,而不是整块替换。同样这对输入,深度合并给出 {"a":{"x":1,"y":2}},两边的键都留下了。
一句话记法:同名键两边都是对象,深度合并递归进去逐键拼;同名键是标量(数字、字符串、布尔),才走"后者覆盖前者"。这个差别在配置文件和局部更新场景里,直接决定你会不会丢数据。
后者覆盖前者,这是分层配置的命根子
深度合并在遇到标量冲突时,规则很简单:后者胜出。两个输入时 B 盖 A;再加第三个 C,那就 C 盖 B。比如 {"port":8080} 合并 {"port":9090},port 这个标量键解析成 9090。
这种从左到右的折叠不是随便定的,它正好对上分层配置的工作方式:先放基底文件,再叠环境覆盖,最后叠本地覆盖。越靠后越具体,最具体的值留到最后。所以输入顺序不是细节,是语义本身,放反了结果就反了。
数组没有标准答案,四种策略按数据选
对象能递归,数组却没有唯一正确的合并方式,只能由你按数据形态选。JSON 合并工具给了四种:
- 替换(默认):后一个数组整个顶掉前一个,
[1,2]盖[3]得[3]。 - 拼接:接在后面,
[1,2]再[3,4]得[1,2,3,4]。 - 去重拼接:先接再按 JSON 值去重,
[1,2]和[2,3]得[1,2,3]。 - 按索引:逐个元素合并,
[{x:1}]盖[{y:2}]得[{x:1,y:2}],较长的一截保留。
最容易踩的就是默认的"替换"。你本想把两份 tags 列表合起来,策略却停在替换上,旧条目悄悄全没了。两份列表都该出力时,记得换成拼接或去重拼接。
一个完整的输入输出例子
我自己最常用的场景是叠配置。某次基底里 logging 写得很全,生产只想改个级别,我把这两份分别粘进 A 和 B:
A:
{"db":{"host":"localhost","port":5432},"logging":{"level":"info","format":"json"}}
B:
{"db":{"host":"prod.internal"},"logging":{"level":"warn"}}
深度合并后得到:
{"db":{"host":"prod.internal","port":5432},"logging":{"level":"warn","format":"json"}}
看清楚:db.host 被生产值覆盖,db.port 我没动它就保留;logging.level 从 info 变 warn,logging.format 这个我没在 B 里重写的键完整留下。换成浅合并,整个 db 和 logging 会被 B 那两个残缺对象顶替,port 和 format 当场消失。这就是深度合并值钱的地方。
三类高频用途
第一类是配置叠加。基底放合理默认,环境文件只列变化项,合并产出服务真正运行用的有效配置,没覆盖的嵌套块原样保留。
第二类是默认值加用户设置。把出厂默认当基底 A,用户改过的设置当 B,深度合并让用户只需要存"和默认不一样的那几项",而不是整份配置照抄一遍,后续默认值更新也不会被用户的旧副本覆盖掉。
第三类是补丁落地。一个 PATCH 请求只返回它改过的字段,你想看到补丁落地后资源长什么样,把当前资源当 A、patch 体当 B 合并一下,结果就是合并后的状态。数组策略在这里决定返回的列表是替换还是追加。
顺手提一句安全:合并时会跳过 __proto__、constructor、prototype 这些键,恶意数据没法借合并污染原型链。整个过程在浏览器本地跑,不上传。
合并前如果两侧格式不齐,先用 JSON 格式化工具过一遍,把 JSON5、注释、尾逗号这些严格 JSON 不认的东西清掉,再合并就不会有某一侧被悄悄踢出去。
Made by Toolora · Updated 2026-06-13