没有遗漏的 js 对象属性合并,flatten/unflatten/assignObject

背景

在 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 属性完全替代了,这在某些场景下是不符合预期的。

解决

  1. 将当前对象打平(flatten),这样其子节点的访问路径就是个唯一的 “属性名称”
  2. 通过 Object.assign() 方法合并打平后的属性
  3. 展开(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
  ]
}
添加新评论