Appearance
2755. 深度合并两个对象
ts
type JSONValue =
| null
| boolean
| number
| string
| JSONValue[]
| { [key: string]: JSONValue }
type JSONObject = { [key: string]: JSONValue }
function isObject(value: JSONValue): value is JSONObject {
return typeof value === 'object'
}
const type = (obj: any) => Object.prototype.toString.call(obj).slice(8, -1)
function deepMerge(obj1: JSONValue, obj2: JSONValue): JSONValue {
if (obj1 === null) return obj2
if (type(obj1) !== type(obj2)) return obj2
if (Array.isArray(obj1) && Array.isArray(obj2)) {
const result: JSONValue = [...obj1]
for (let i = 0; i < obj2.length; i++) {
if (isObject(result[i])) {
result[i] = deepMerge(result[i], obj2[i])
} else {
result[i] = obj2[i]
}
}
return result
}
if (isObject(obj1) && isObject(obj2)) {
const result: JSONObject = { ...obj1 }
for (const key in obj2) {
if (key in obj1) {
result[key] = deepMerge(obj1[key], obj2[key])
} else {
result[key] = obj2[key]
}
}
return result
}
return obj2
}合并规则
deepMerge 的语义可以看成 Object.assign 再叠加一层深度递归:
- obj1 为 null → 返回 obj2。 null 被视为「无值」,obj2 直接覆盖
- 类型不同 → 返回 obj2。 如果一个是数组一个是对象,无法合并,obj2 覆盖
- 都是数组 → 按索引合并。 obj2 的元素覆盖 obj1 的同索引元素;如果 obj2 更长,多余的元素追加
- 都是对象 → 按 key 合并。 同名 key 递归合并;仅在 obj2 中存在的 key 直接添加
- 基本类型 → 返回 obj2。 数字、字符串等直接覆盖
展开运算符的策略
const result: JSONObject = { ...obj1 } 先复制 obj1 的所有属性,这是一个浅拷贝。然后遍历 obj2 的 key,同名 key 递归合并,新 key 直接赋值。这样 obj1 中 obj2 没有的 key 得以保留——这是「合并」而非「替换」。
数组的处理不同:const result: JSONValue = [...obj1] 同样浅拷贝,然后逐索引处理。obj2[i] 存在而 result[i] 是对象时,递归合并;否则直接覆盖。
与 Ramda.mergeDeepRight 的对比
Ramda 的 mergeDeepRight(或 mergeDeepLeft)有明确的偏向规则,right 覆盖 left。这里的实现也是 obj2 优先,效果上更接近 mergeDeepRight。区别在于 Ramda 还会处理 Map、Set、函数等类型,而这里的 JSONValue 约束把输入范围收得更窄。
