【问题标题】:Update Javascript Object, nested Data - insert only if not updateable更新 Javascript 对象,嵌套数据 - 仅在不可更新时插入
【发布时间】:2016-10-14 11:13:56
【问题描述】:

假设我有以下数据

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 包含的数组具有基于 scores 中的 idsites 中的 sid 的唯一元素。我想要一个具有以下魔力的函数:

//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 实现了类似的事情?

【问题讨论】:

  • 到目前为止你尝试了什么?
  • 我尝试编写自己的函数,但当我注意到我还需要递归时,结果并不好。然后我找到了上面的 lodash 函数,想到之前可能有人遇到过同样的问题。
  • 我注意到合并对对象和嵌套对象非常有效。也许我只是尝试将我的数据结构从scores : [{id: s1, points:2},...] 转换为scores:{s1:{points:2},...}

标签: javascript arrays object insert lodash


【解决方案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 没有内置的对象差异功能,因此有必要开发类似的功能,以防您想要旧对象和修改后的对象之间的差异(您还必须@ 987654324@对象先有副本,可以比较)。

我介绍的函数的思路是尝试获取obj的key(就是我们要在src中修改的key),并检查它是否存在并且是一个数组。如果是这样,我们只需添加两个数组,更新src 中与obj 中具有相同id 的数组。由于sitesscoreshistoryidsid,我不得不为_.unionWith 函数的比较器添加更多逻辑。

如果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

【讨论】:

  • 非常感谢您的帮助。不幸的是,当一个人试图一次设置多个值时它不起作用(我不得不承认我上面的例子没有涵盖,但现在是)。见这里:https://jsfiddle.net/qyf3hbe1/。我想出了以下解决方案:https://jsfiddle.net/r0507fyh/
  • @niklas 谢谢!我已经用可能的问题解决方案更新了答案:使用当前方法,一次更新一个属性。它可能会起作用!希望对您有所帮助。
  • 非常感谢。我发现性能(由于您的解决方案的许多循环比我编写的自定义函数慢约 10 倍。见这里jsfiddle.net/r0507fyh/3
【解决方案2】:

我编写了一个递归且非常高效的解决方案。 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;
}

【讨论】:

    猜你喜欢
    • 2021-11-27
    • 2023-01-22
    • 2019-03-13
    • 2020-09-24
    • 1970-01-01
    • 2016-12-25
    • 2020-12-30
    • 1970-01-01
    • 2020-02-08
    相关资源
    最近更新 更多