如果将键作为字符串给出,如何部分更新嵌套对象?

时间:2015-09-28 03:12:58

标签: javascript underscore.js

我有下划线。

我有:

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';

我希望能够在一行代码中设置所有道具,但也保持其他属性不受影响。

如果我这样做:

_.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;
};

并运行如下:

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;

};

如何编辑此功能以包含合并行为?

2 个答案:

答案 0 :(得分: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 :(得分:-1)

是否可以将库从下划线更改为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"
  }
}