如何通过给定的字符串键部分更新嵌套对象?

3
我有下划线。
我有:
var person = {
  personal: {
    fname: 'Victor',
    lname: 'Lee',
    address: {
      street: '1234 Main',
      state: {
        abbrName: 'CA',
        fullName: 'California',
        timezone: 'PST'
      },
      zip: '94043'
    }
  }
};

我想要更新多个属性,但保留其他属性不变。
不用写三行分别操作:
  person.personal.address.state.abbrName = 'OR';
  person.personal.address.state.fullName = 'Oregon';
  person.personal.address.zip = '97062';

我想能够在一行代码中设置所有props,但也要保留其他属性不变。
如果我这样做:
_.extend(person.personal.address, {
  state: {
    abbrName: 'OR',
    fullName: 'Oregon'
  },
  zip: '97032'
});

结果对象已经去掉了时区。
{
  personal: {
    fname: 'Victor',
    lname: 'Lee',
    address: {
      street: '1234 Main',
      state: {
        abbrName: 'CA',
        fullName: 'California',
      },
      zip: '94043'
    }
  }
};

这样的东西将会是理想的:
var updateObj = function(obj, key, value){
  // stuff
  return obj;
};

and run like:

updateObj(person, 'personal.address', {
  state: {
    abbrName: 'OR',
    fullName: 'Oregon'
  },
  zip: '97032'
});

到目前为止,我已经做到了这一点,但它每次只能完全覆盖一个属性。
var updateObjectWithStringKey = function(obj, key, value) {
  if (typeof key === "string"){
    key = key.split(".");
  };

  if (prop.length > 1) {
    var e = key.shift();
    updateObjectWithStringKey(obj[e] =
      typeof obj[e] == 'object' ? obj[e] : {},
      key,
      value);
  } else {
    obj[key[0]] = value;
  };

  return obj;

};

编辑:
好的,我觉得我接近了:
var MergeRecursive = function(destination, source) {

  for (var p in source) {

    if ( typeof source[p] == 'object' ) {
      destination[p] = MergeRecursive(destination[p], source[p]);
    } else {
      destination[p] = source[p];
    };

  };

  return destination;

};

即使信息夹在对象的不同层级之间,这也会将其合并。
var person = {
  personal: {
    fname: 'Victor',
    lname: 'Lee',
    address: {
      street: '1234 Main',
      state: {
        abbrName: 'CA',
        fullName: 'California',
        timezone: 'PST'
      },
      zip: '94043'
    }
  }
};

var updatedInfo = {
  personal: {
    address: {
      state: {
        abbrName: 'OR',
        fullName: 'Oregon',
        capital: 'Salem'
      },
      zip: '97062'
    },
  }
};

MergeRecursive(person, updatedInfo);

返回
{
  personal: {
    fname: 'Victor',
    lname: 'Lee',
    address: {
      street: '1234 Main',
      state: {
        abbrName: 'OR',
        fullName: 'Oregon',
        timezone: 'PST',
        capital: 'Salem'
      },
      zip: '97062'
    }
  }
}

但是像我说的一样,我想提供一个字符串路径来更新我想要更新的对象部分:
updateObj(person, 'personal.address', {
  state: {
    abbrName: 'OR',
    fullName: 'Oregon',
    capital: 'Salem'
  },
  zip: '97062'
});

这个函数可以实现它,但不具有上述的合并行为。
var updateObjectWithStringProp = function(obj, prop, value) {

  if (typeof prop === "string") {
    var prop = prop.split('.');
  }

  if (prop.length > 1) {
    var p = prop.shift();
    if (obj[p] == null || typeof obj[p] !== 'object') {
      obj[p] = {};
    }
    updateObjectWithStringProp(obj[p], prop, value);
  } else {
    obj[prop[0]] = value;
  }

  return obj;

};

我该如何编辑这个函数以包括合并行为?

1
在上述特定情况下,您为什么不尝试使用_.extend(person.personal.address, { state: 'MO', zip: 44444 })呢? - Ken Franqueiro
1
你为什么要使用 Object.prototype.toString.call(obj[e]) === "[object Object]" 而不是 typeof obj[e] == 'object' - RobG
好的,我已经编辑了原始问题。最大的问题是更新夹在对象两个级别之间的信息。 - fuzzybabybunny
@Jorg - 那是一个相当普通的答案。try..catch 完全没有必要,而且 obj2[p].constructor == Object 在许多情况下都会失败。 - RobG
Jorg的解答在我的情况下起作用。有没有更健壮的方法来实现它?我个人也很难理解递归。 - fuzzybabybunny
显示剩余4条评论
2个回答

0

你可以使用递归,但要记住,对于具有高嵌套的大对象,它可能会存在问题。

注意:在编写这样的函数之前,应考虑以下几点:

  • 两个值都可以是简单对象
  • 两个值都可以是对象数组
  • 两个值都可以是数组
  • 新对象可以有额外的参数。您应该允许向原始对象添加值吗?
  • 源对象可以为空吗?如果是,您应该返回undefined还是newValues

还可以有其他参数。

function mergeObject(source, newValue, allowNewProps) {
  if (typeof newValue === "object" && typeof source === "object") {
    for (var k in newValue) {
      if (Array.isArray(newValue[k]) && Array.isArray(source[k])) {
        for (var i = 0; i < newValue[k].length; i++) {
          mergeObject(source[k][i], newValue[k][i]);
        }
      }
      if (typeof newValue[k] === "object") {
        mergeObject(source[k], newValue[k]);
      } else if (source[k] !== undefined || allowNewProps) {
        source[k] = newValue[k];
      }
    }
  }
}

-1

你能否将你的库从underscore更改为lodash

这样,你将拥有相同的(以及更多)可用函数,包括_.defaultsDeep

你可以在一行代码中执行合并操作,同时保留你的时区。

var person = {
  personal: {
    fname: 'Victor',
    lname: 'Lee',
    address: {
      street: '1234 Main',
      state: {
        abbrName: 'CA',
        fullName: 'California',
        timezone: 'PST'
      },
      zip: '94043'
    }
  }
};
var update = {
  personal: { 
    address: { 
      state: { 
        abbrName: 'OR', 
        fullName: 'Oregon' 
      }, 
      zip: '97032'
    }
  }
}
var result = _.defaultsDeep(update, person);

结果将解析为:

result = {
  "personal":{
    "address":{
      "state":{
        "abbrName":"OR",
        "fullName":"Oregon",
        "timezone":"PST"
      },
      "zip":"97032",
      "street":"1234 Main"
    },
    "fname":"Victor",
    "lname":"Lee"
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接