使用另一个对象的值递归更新 JavaScript 对象

Update a JavaScript object recursively using values from another object

我想向嵌套的 JavaScript 对象添加一些数据,但是我不知道对象树中已经存在多少。如果该结构不存在,我想添加该结构,但如果它在那里标记有一些其他默认数据,我也想保留相同的信息。

理想情况下,我不想为此使用第三方库。

在我的示例中,我有以下对象结构,将有这些可用的无限组合,因此如果它尚不存在,则需要为我生成该结构。

{
  level1: {
    level2: {
      level3: {
        property1: 'test'
      }
    }
  }
}

我希望能够为其提供一些值,创建结构 - 如果存在结构,则添加一些默认值,如果不存在,则使用默认值创建结构。

我有下面的,它可以完成工作,但我觉得它很乱,我想知道是否有更好的方法来实现它?

const updateNestedObject = (obj, level1, level2, level3, defaultProps) => {
  if (obj && obj[level1] && obj[level1][level2] && obj[level1][level2][level3]) {
    obj[level1][level2][level3] = { ...defaultProps, ...obj[level1][level2][level3] }
  } else if (obj && obj[level1] && obj[level1][level2] && !obj[level1][level2][level3]) {
    obj[level1][level2][level3] = defaultProps
  } else if (obj && obj[level1] && !obj[level1][level2]) { 
    obj[level1][level2] = { [level3]: defaultProps }
  } else if (obj && !obj[level1]) { 
    obj[level1] = { [level2]: { [level3]: defaultProps } }
  } else {
    obj = { [level1]: { [level2]: { [level3]: defaultProps } } }
  }
  return obj
}

你可以做这样的事情,在键上循环并逐层遍历对象。每个循环我们都将一个外部变量 - level - 设置为当前变量,因此我们不必使用之前的键来达到当前级别。即,我们避免必须做 obj[ level1 ][ level2 ][ level3 ] = ...,相反,我们可以做 level = ...

这样的话,我觉得还是传一个参数比较好keys,就是一个数组。有了这个,您就不会仅限于 3 个嵌套键,而是可以传递任何您想要的数字。

const updateNestedObject = ( obj, keys, defaultProps ) => {
  let level = obj;
  for ( let i = 0; i <= keys.length-1; i++ ) {
    let key = keys[ i ];

    if ( i === keys.length-1 ) {
      level[ key ] = { ...defaultProps };
      return;
    }

    if ( !level[ key ] ) {
      level[ key ] = {};
    }

    level = level[ key ];
  }
};

const object = {};
updateNestedObject( object, [ "foo", "bar", "baz" ], { property: "test" } )
console.log( object );

const object2 = { foo: { bar: {} } };
updateNestedObject( object2, [ "foo", "bar", "baz" ], { property: "test" } )
console.log( object2 );

const object3 = { foo: { bar: {} } };
updateNestedObject( object3, [ "foo", "bar" ], { property: "test" } )
console.log( object3 );

请注意,这确实会更改对象 in-place。

这可以通过递归来完成。下面是如何实施它的大纲(未针对所有情况进行测试):

function mergeRecursive(targetObject, sourceObject) {
  Object.keys(sourceObject).forEach(function(key) {
    if (typeof sourceObject[key] === "object") {
      if (targetObject[key] === undefined) {
        targetObject[key] = {};
      }
      mergeRecursive(targetObject[key], sourceObject[key]);
    } else {
      targetObject[key] = sourceObject[key];
    }
  });
}
let foo = {
  "level1": {
    "bar": "baz"
  }
};
let bar = {
  level1: {
    level2: {
      level3: {
        property1: "test"
      }
    }
  }
};
mergeRecursive(foo, bar);
console.log(foo);

我经常使用的 setPath function 的一个小变体会以相当直接的方式执行此操作:

const mixDeep = ([p, ...ps]) => (v) => (o) =>
  p == undefined ? v : Object .assign (
    Array .isArray (o) || Number .isInteger (p) ? [] : {},
    {...o, [p]: ps .length ? mixDeep (ps) (v) ((o || {}) [p]) : {...v, ...(o || {}) [p]}},
  ) 

const myFunc = 
  mixDeep (['level1', 'level2', 'level3']) ({some: 'default', props: 'here'}) 

console .log (myFunc ({foo: 'bar'}))
console .log (myFunc ({foo: 'bar', level1: {baz: 'qux'}}))
console .log (myFunc ({foo: 'bar', level1: {baz: 'qux', level2: {corge: 'grault'}}}))
console .log (myFunc ({foo: 'bar', level1: {baz: 'qux', level2: {corge: 'grault', level3: {garply: 'waldo'}}}}))
.as-console-wrapper {max-height: 100% !important; top: 0}

setPath 设置 路径上的值,根据需要构建中间节点。 mixDeep 将值混合 到路径末尾的值中,再次构建缺少的任何中间节点。