背景
在 javascript 中,当通过 Object.assign(...objs) 方法合并对象属性时,会出现部分子节点数据缺失的情况。
比如如下示例
let objA = {
a: {
b: 'b'
}
};
Object.assign(objA, {
a: {
c: 'c'
}
});
console.log(objA);
期望得到的 objA
结果是
a: {
b: 'b',
c: 'c',
}
但实际结果却是
a: {
c: 'c'
}
即 objA
的属性 a
被后面对象的 a
属性完全替代了,这在某些场景下是不符合预期的。
解决
- 将当前对象打平(flatten),这样其子节点的访问路径就是个唯一的 “属性名称”
- 通过 Object.assign() 方法合并打平后的属性
- 展开(unflatten) 最后的结果。
实现代码如下
function _flatten(obj, sep = '.') {
function recurse(curr, prefix, res = {}) {
if (Array.isArray(curr)) {
curr.forEach((item, index) => {
recurse(item, prefix ? `${prefix}${sep}${index}` : `${index}`, res);
});
} else if (curr instanceof Object) {
const keys = Object.keys(curr);
if (keys.length) {
keys.forEach((key) => {
recurse(curr[key], prefix ? `${prefix}${sep}${key}` : `${key}`, res);
});
} else if (prefix) {
res[prefix] = curr;
}
} else {
res[prefix] = curr;
}
}
let output = {};
recurse(obj, '', output);
return output;
}
function _unflatten(obj, sep = '.') {
let output = {};
Object.keys(obj).forEach(key => {
if (key.indexOf(sep) !== -1) {
const keyArr = key.split('.').filter(item => item !== '');
let currObj = output;
keyArr.forEach((k, i) => {
if (typeof currObj[k] === 'undefined') {
if (i === keyArr.length - 1) {
currObj[k] = obj[key];
} else {
currObj[k] = isNaN(keyArr[i + 1]) ? {} : [];
}
}
currObj = currObj[k];
});
} else {
output[key] = obj[key];
}
});
return output;
}
function _assignObject(targetObj, ...objs) {
const res = _flatten(targetObj);
objs.forEach(obj => {
Object.assign(res, _flatten(obj));
});
Object.assign(targetObj, _unflatten(res));
return targetObj;
}
使用示例
let objA = {
a: {
b: [
{ c: 'c' },
{ d: 'd' }
]
}
};
_assignObject(objA, { a: { x: 'x' } }, { y: [1, 2, 3] });
console.log(JSON.stringify(objA, null, 2));
output:
{
"a": {
"b": [
{
"c": "c"
},
{
"d": "d"
}
],
"x": "x"
},
"y": [
1,
2,
3
]
}