更新Javascript对象,嵌套数据 - 仅在不可更新时插入

时间:2016-10-14 11:13:56

标签: javascript arrays object insert lodash

我们说我有以下数据

var obj = {
  test: 'somedata',
  scores: [
    {
      "points":99, 
      "id":"x12"
    },
    {
      "points":21, 
      "id":"x13"
    }
  ],
  sites: [
    {
      "exercises":21, 
      "sid":"s12"
    },
    {
      "exercises":23, 
      "sid":"s11"
    }
  ],
  history: {
    key: 'value',
    commits: [
      {
         id: 1,
         value: 'thank you'
      }
    ]
  }
}

请注意,scoressites包含基于id中基于scores并基于sidsites的唯一元素的数组。我想要一个具有以下魔力的功能:

//will **update** obj.test to 'newdata' and return {test:'newdata'}
magicUpdate(obj, {test:'newdata'}) 

//will **insert** obj.newkey with with value 'value' and return {newkey: 'value'}
magicUpdate(obj, {newkey: 'value'})

//will do nothing and return {}
magicUpdate(obj, {scores: []})

//will **update** scores[0] and return {scores:[{points:3, id: "x12"}]}, as id "x12" is already in the array at index 0
magicUpdate(obj, {scores:[{points:3, id: "x12"}])

//will **insert** {points:3, id: "x14"} into obj.scores and return {scores:[{points:3, id: "x14"}]}
magicUpdate(obj, {scores:[{points:3, id: "x14"}]})

//will **update** sites[0] and return {sites:[{exercises:22, sid: "s12"}]}, as id "s12" is already in the array at index 0
magicUpdate(obj, {sites:[{exercises:22, sid: "s12"}])

//will **insert** {exercises:10, sid: "s14"} into obj.sites and return {sites:[{exercises:10, sid: "s14"}]}
magicUpdate(obj, {sites:[{exercises:10, sid: "s14"}]})

//and also be recursive ...
//will **update** obj.history.commits[0]
magicUpdate(obj, {'history.commits': [{id:1, value: 'changed'}]});

我看过.update 进行递归,但只有在传递path时才应该自动确定。然后有 .merge在内部使用_.baseMerge并且非常接近我需要的东西但我不理解该函数的签名。

_.merge(
{scores:[{id: 12, points:10}, {id: 13, points:10}]}, 
{scores:[{id: 14, points:10}, {id: 15, points:10}]}
) 
// returns {scores:[{id: 14, points:10}, {id: 15, points:10}]} not the fully merged array

有人能指出我的方向是好还是用lodash做过类似的事情?

2 个答案:

答案 0 :(得分:1)

您在帖子中提到的magicUpdate功能确实可以使用lodash功能实现。

对于此实施,我主要使用_ .get_ .set_ .unionWith,但我确信它可以通过其他方式实现:

// src will be mutated. For simplicity's sake, obj is an object with only one property that represents the changes to make to src
function magicUpdate(src, obj) {
  var key = _.first(_.keys(obj)),
    value = _.get(obj, key),
    srcValue = _.get(src, key),
    comparator = function(a, b) {
      var idKey = _.isUndefined(a.id) ? 'sid' : 'id';
      return a[idKey] === b[idKey];
    }

  if (_.isArray(srcValue)) {
    value = _.unionWith(value, srcValue, comparator);
  }

  return _.set(src, key, value);
}

您可能已经注意到查看代码,返回类型是变异对象,而不是您要求的内容。我不太确定你想要什么作为回报价值。

无论如何,Lodash没有内置的物体差异功能,所以如果你想要旧物体和修改过的物体之间的区别(你&#39),就必须开发这样的东西。 ; d还必须_ .clone首先获得副本并能够进行比较。

我提出的函数的想法是尝试获取obj的键(它是我们想要在src中修改的键)并检查它是否存在并且是一个数组。如果是这样,我们只需添加两个数组,更新srcid objsites的数组。由于scoreshistoryidsid_.unionWith这一事实我不得不在{{1}的比较器中添加更多逻辑功能。

如果key不存在或不是数组,我们只需将其设置为src

如果你想玩它,你可以使用fiddle。希望它有所帮助。

<强>更新

我的第一个解决方案是针对一次更新的一个属性。但是,似乎可以同时更新多个。

一个快速的解决方案是使用更新迭代对象并一次更新一个属性。

function updateProperty(src, obj) {
  var key = _.first(_.keys(obj)),
    value = _.get(obj, key),
    srcValue = _.get(src, key),
    comparator = function(a, b) {
      var idKey = _.isUndefined(a.id) ? 'sid' : 'id';
      return a[idKey] === b[idKey];
    }

  if (_.isArray(srcValue)) {
    value = _.unionWith(value, srcValue, comparator);
  }

  return _.set(src, key, value);
}

function magicUpdate(obj, src) {
  _.forEach(src, function(value, key) {
      updateProperty(obj, _.pick(src, key));
  });
  return obj;
}

Fiddle

答案 1 :(得分:0)

我写了一个递归且性能相当的解决方案。 See this fiddle.

function mergeRecursive(obj1, obj2) {
  if (obj1.constructor == Array) {
    for (var i = 0; i < obj1.length; i++) {
      if (obj1[i].id == obj2.id) {
        obj1[i] = obj2;
        return obj1;
      }
    }
    obj1.push(obj2);
    return obj1;
  }
  for (var p in obj2) {
    // Property in destination object set; update its value.
    if (obj2[p].constructor == Array) {
      obj2[p].forEach(function(arrayElement) {
        obj1[p] = MergeRecursive(obj1[p], arrayElement);
      });
    } else if (obj2[p].constructor == Object) {
      obj1[p] = MergeRecursive(obj1[p], obj2[p]);
    } else {
      obj1[p] = obj2[p];
    }
  }
  return obj1;
}