问题
I have an array:
[
{
assignmentId:17,
email:"john.smith@email.com"
expectation: "Make sure to proofread!",
firstName:"John"
id:23
ignoreForFeedback: true
lastName:"Smith"
level:2
levelFraction:null
score:35
},
{
assignmentId:17
countsPerCategory: Array(4)
email:"john.smith@email.com"
firstName:"John"
frequentErrors: Array(5)
id:23
ignoreForGrading: true
lastName:"Smith"
},
{
assignmentId:17,
email:"cl@email.com"
expectation: "cite sources",
firstName:"Cindy"
id:45
ignoreForFeedback: true
lastName:"Lee"
level:2
levelFraction:null
score:32
},
{
assignmentId:17
countsPerCategory: Array(4)
email:"cl@email.com"
firstName:"Cindy"
frequentErrors: Array(5)
id:45
ignoreForGrading: true
lastName:"Lee"
}
]
I want to combine the Objects with the same 'id' into the same object within the array. Their common keys should also be combined (eg: 'firstName', 'email'). Can someone suggest the best way to do this? Either with ES6 or Lodash
回答1:
You can use lodash#groupBy to group all items in the array by id and then use lodash#map with an iteratee of lodash#assign that is wrapped with a lodash#spread to make the array callback as a list of arguments for lodash#assgin.
var result = _(array)
.groupBy('id')
.map(_.spread(_.assign))
.value();
var array = [
{
assignmentId:17,
email:"john.smith@email.com",
expectation: "Make sure to proofread!",
firstName:"John",
id:23,
ignoreForFeedback: true,
lastName:"Smith",
level:2,
levelFraction:null,
score:35
},
{
assignmentId:17,
countsPerCategory: Array(4),
email:"john.smith@email.com",
firstName:"John",
frequentErrors: Array(5),
id:23,
ignoreForGrading: true,
lastName:"Smith"
},
{
assignmentId:17,
email:"cl@email.com",
expectation: "cite sources",
firstName:"Cindy",
id:45,
ignoreForFeedback: true,
lastName:"Lee",
level:2,
levelFraction:null,
score:32
},
{
assignmentId:17,
countsPerCategory: Array(4),
email:"cl@email.com",
firstName:"Cindy",
frequentErrors: Array(5),
id:45,
ignoreForGrading: true,
lastName:"Lee"
}
];
var result = _(array)
.groupBy('id')
.map(_.spread(_.assign))
.value();
console.log(result);
body > div { min-height: 100%; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
Here's an alternative solution that uses Array#filter which takes advantage of the 2nd argument of the Array#filter which gives context to the filter's callback function. We use the this context as a mechanism to store cached objects by their id and then use this to decide whether to retain these objects from the array or not.
var result = array.filter(function(v) {
return this[v.id]?
!Object.assign(this[v.id], v):
(this[v.id] = v);
}, {});
var array = [
{
assignmentId:17,
email:"john.smith@email.com",
expectation: "Make sure to proofread!",
firstName:"John",
id:23,
ignoreForFeedback: true,
lastName:"Smith",
level:2,
levelFraction:null,
score:35
},
{
assignmentId:17,
countsPerCategory: Array(4),
email:"john.smith@email.com",
firstName:"John",
frequentErrors: Array(5),
id:23,
ignoreForGrading: true,
lastName:"Smith"
},
{
assignmentId:17,
email:"cl@email.com",
expectation: "cite sources",
firstName:"Cindy",
id:45,
ignoreForFeedback: true,
lastName:"Lee",
level:2,
levelFraction:null,
score:32
},
{
assignmentId:17,
countsPerCategory: Array(4),
email:"cl@email.com",
firstName:"Cindy",
frequentErrors: Array(5),
id:45,
ignoreForGrading: true,
lastName:"Lee"
}
];
var result = array.filter(function(v) {
// does this `id` exist?
return this[v.id]?
// assign existing object with the same id
// from the `this` cache object. Make sure
// to negate the resulting object with a `!`
// to remove this value from the array
!Object.assign(this[v.id], v):
// Assign the value from the `this` cache.
// This also retains this value from the existing
// array
(this[v.id] = v);
}, {});
console.log(result);
body > div { min-height: 100%; top: 0; }
回答2:
You can use JavaScript's built in Array.reduce() method. The idea is you can create a map with the IDs and use lodash.merge() method (or whatever method you choose for merging objects) to merge all of the objects with the same ID into a single object. Then you can use .map() on the idMap you created to get the objects back into a single array.
var data = [{
assignmentId: 17,
email: "john.smith@email.com",
expectation: "Make sure to proofread!",
firstName: "John",
id: 23,
ignoreForFeedback: true,
lastName: "Smith",
level: 2,
levelFraction: null,
score: 35
},
{
assignmentId: 17,
countsPerCategory: Array(4),
email: "john.smith@email.com",
firstName: "John",
frequentErrors: Array(5),
id: 23,
ignoreForGrading: true,
lastName: "Smith"
},
{
assignmentId: 17,
email: "cl@email.com",
expectation: "cite sources",
firstName: "Cindy",
id: 45,
ignoreForFeedback: true,
lastName: "Lee",
level: 2,
levelFraction: null,
score: 32
},
{
assignmentId: 17,
countsPerCategory: Array(4),
email: "cl@email.com",
firstName: "Cindy",
frequentErrors: Array(5),
id: 45,
ignoreForGrading: true,
lastName: "Lee"
}
];
var idMap = data.reduce(function(result, current) {
if (result[current.id] == null) {
result[current.id] = current;
} else {
_.merge(result[current.id], current);
}
return result;
}, {});
var results = Object.keys(idMap).map(function(key) {
return idMap[key];
});
console.log(results);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
回答3:
What I can suggest is to use a combination of forEach() and some() methods to iterate the array elements and test if the iterated object id is already processed or not.
This is the solution:
var merged = [];
arr.forEach(function(item) {
var idx;
var found = merged.some(function(el, i) {
idx = el.id === item.id ? i : null;
return el.id === item.id;
});
if (!found) {
merged.push(item);
} else if (idx !== null) {
for (k in Object.keys(item)) {
if (item.hasOwnProperty(k)) {
merged[idx][k] = item[k];
}
}
}
});
Working Demo:
var arr = [{
assignmentId: 17,
email: "john.smith@email.com",
expectation: "Make sure to proofread!",
firstName: "John",
id: 23,
ignoreForFeedback: true,
lastName: "Smith",
level: 2,
levelFraction: null,
score: 35
},
{
assignmentId: 17,
countsPerCategory: [],
email: "john.smith@email.com",
firstName: "John",
frequentErrors: [],
id: 23,
ignoreForGrading: true,
lastName: "Smith"
},
{
assignmentId: 17,
email: "cl@email.com",
expectation: "cite sources",
firstName: "Cindy",
id: 45,
ignoreForFeedback: true,
lastName: "Lee",
level: 2,
levelFraction: null,
score: 32
},
{
assignmentId: 17,
countsPerCategory: [],
email: "cl@email.com",
firstName: "Cindy",
frequentErrors: [],
id: 45,
ignoreForGrading: true,
lastName: "Lee"
}
];
var merged = [];
arr.forEach(function(item) {
var idx;
var found = merged.some(function(el, i) {
idx = el.id === item.id ? i : null;
return el.id === item.id;
});
if (!found) {
merged.push(item);
} else if (idx !== null) {
for (k in Object.keys(item)) {
if (item.hasOwnProperty(k)) {
merged[idx][k] = item[k];
}
}
}
});
console.log(merged);
回答4:
Thank you for your help everyone, but I ended up going with my own implementation.
let ids = [];
let combinedUsers = [];
users.forEach(function (user) {
ids.push(user.id);
});
ids = _.uniq(ids);
ids.forEach(function(id){
let user = users.filter(function(userObj){
return id === userObj.id
});
if(user.length > 1){
user = Object.assign(user[0], user[1]);
combinedUsers.push(user);
} else {
combinedUsers.push(user[0]);
}
});
return combinedStudents;
来源:https://stackoverflow.com/questions/43741936/merge-objects-in-same-array-based-on-common-value-in-javascript