功能说明
该示例实现 RFC 7386 的 JSON Merge Patch。补丁是普通对象时递归合并:键值为 null 表示删除目标中的同名键,嵌套对象继续递归,其他值直接替换;补丁本身不是对象时则整体替换目标。数组按整体替换处理,不逐项合并。函数返回新对象,适合配置中心下发增量配置、PATCH 接口更新资源或本地状态合并。
关键代码
isPlainObject 排除 null、数组与自定义类实例,只有原型为 Object.prototype 或 null 的对象才参与递归,防止把 Date、Map 等误当配置对象。顶层结果用展开运算符浅拷贝,未触碰的嵌套值仍共享引用,但被补丁修改的分支会生成新对象。cloneJson 使用 structuredClone 复制替换值,避免调用方之后修改补丁影响结果。键名为 proto、constructor 或 prototype 时直接抛错,阻断原型污染。
使用注意
该算法无法表达把键显式设置为 null,因为 null 在 RFC 7386 中专门表示删除;需要保存 null 时应改用 JSON Patch。补丁必须由 JSON 可表示的值组成,undefined、函数与 Symbol 不属于合法输入。由于未修改的嵌套对象是共享引用,若调用方要求完全不可变,应在应用后再做深度冻结或深拷贝。函数只做结构合并,不校验业务 Schema。
验证方式
用嵌套对象测试新增、覆盖与 null 删除;确认数组被整体替换而不是逐项合并;传入标量补丁验证目标被替换;断言原目标与补丁对象都未被修改;构造含 proto 的补丁应抛 TypeError;对非对象目标应用对象补丁时返回只含补丁字段的新对象。
代码示例
function isPlainObject(value) {
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
}
function cloneJson(value) {
return value === undefined ? undefined : structuredClone(value);
}
export function applyMergePatch(target, patch) {
if (!isPlainObject(patch)) return cloneJson(patch);
const result = isPlainObject(target) ? { ...target } : {};
for (const [key, value] of Object.entries(patch)) {
if (key === "__proto__" || key === "constructor" || key === "prototype") {
throw new TypeError("unsafe patch key: " + key);
}
if (value === null) {
delete result[key];
continue;
}
result[key] = applyMergePatch(result[key], value);
}
return result;
}