我们说我有以下数据
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'
}
]
}
}
请注意,scores
和sites
包含基于id
中基于scores
并基于sid
中sites
的唯一元素的数组。我想要一个具有以下魔力的功能:
//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做过类似的事情?
答案 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
中修改的键)并检查它是否存在并且是一个数组。如果是这样,我们只需添加两个数组,更新src
中id
obj
中sites
的数组。由于scores
,history
和id
有sid
和_.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;
}
答案 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;
}