Reputation: 15475
JavaScript - Is there a way to merge 2 objects by a specified key property? The key property in this case is the authorId
.
NOTE: author 3 and author 1 don't have matches, so they shouldn't get merged.
var b = [{"book": "book1", "authorId": 3},{"book": "book2", "authorId":2}];
var a = [{"authorId": 1, "author": "author1"},{"authorId": 2, "author": "author2"}];
var c = a.merge(b);
console.log(c);
expecting:
[{"book": "book2", "authorId": 2, "author": "author2"}]
I'm trying to accomplish here what a SQL JOIN does.
https://jsfiddle.net/x67fpwoj/4/
Upvotes: 0
Views: 103
Reputation: 10909
Assuming you need to specify the field to join on (since you mentioned SQL like syntax) and without worrying too much about performance (if it is then you would need to build an index for the object to be merged from but read the perf note below) then you can use a couple of array methods and a destructuring assignment to merge the object arrays.
const b = [{"book": "book1", "authorId": 3},{"book": "book2", "authorId":2}];
const a = [{"authorId": 1, "author": "author1"},{"authorId": 2, "author": "author2"}];
// Using reduce will allow filtering of unmatched records
const merge = (field, x, z) => x.reduce((out, v) => {
// Make sure the field exists in the object to merge
// Using find will return the first object that matches the criteria (multiple record matching was not mentioned as a concern in the OP)
const match = v.hasOwnProperty(field) && z.find((c) => c[field] === v[field]);
if (match) {
// Only push onto the output array if there is a match
// Use destructuring assignment to merge the matching objects
out.push({...v, ...match});
}
return out;
}, []);
console.log(merge('authorId', a, b));
Perf Note: Be wary with using an index approach for a variety of reasons. For instance when the object to key the merge from is very small compared to the data set to merge with. Building the index might take longer then just searching the array for the relatively small number of matches. You should use them when it makes sense but just noting that it shouldn't be a flat statement to do so.
For completeness, here is the same code with a temp index:
const b = [{"book": "book1", "authorId": 3},{"book": "book2", "authorId":2}];
const a = [{"authorId": 1, "author": "author1"},{"authorId": 2, "author": "author2"}];
const merge = (field, x, z) => {
const index = z.reduce((out, v) => {
if (v.hasOwnProperty(field)) {
out[v[field]] = v;
}
return out;
}, {});
return x.reduce((out, v) => {
const match = index[v[field]];
if (match) {
out.push({...v, ...match});
}
return out;
}, []);
};
console.log(merge('authorId', a, b));
Upvotes: 1
Reputation: 142
I think this will do what you need with clear code:
var c = [];
b.map(item1 => {
a.map(item2 => {
if (item1.authorId === item2.authorId) {
c.push(Object.assign(item1, item2));
}
})
});
console.log(c);
Upvotes: 0
Reputation: 371029
map
by Object.assign
from the item in the other array with the same index:
var b = [{"book": "book1", "authorId": 1},{"book": "book2", "authorId":2}];
var a = [{"authorId": 1, "author": "author1"},{"authorId": 2, "author": "author2"}];
var c = a.map((aItem, i) => Object.assign({}, aItem, b[i]));
console.log(c);
If you can't count on each array being ordered, then you might reduce
into an object indexed by authorId
, and then get that object's values (this has O(N)
complexity rather than a .find
on each iteration, which would have O(N^2)
complexity):
var a = [{"authorId": 1, "author": "author1"},{"authorId": 2, "author": "author2"}];
var b = [{"book": "book1", "authorId": 1},{"book": "book2", "authorId":2}];
const reduceByAuthor = (arr, initial = {}) => arr.reduce((a, item) => {
a[item.authorId] = Object.assign((a[item.authorId] || {}), item);
return a;
}, initial);
const indexedByAuthor = reduceByAuthor(a);
reduceByAuthor(b, indexedByAuthor);
console.log(Object.values(indexedByAuthor));
If you also can't count on each item having a match in the other array, then reduce
twice, with the second reduce
assigning to the accumulator only if a match was found in the first indexed object (quicker than filtering
afterwards):
var b = [{"book": "book1", "authorId": 3},{"book": "book2", "authorId":2}];
var a = [{"authorId": 1, "author": "author1"},{"authorId": 2, "author": "author2"}];
const aIndexed = a.reduce((a, item) => {
a[item.authorId] = item;
return a;
}, {});
const c = Object.values(b.reduce((a, bItem) => {
const { authorId } = bItem;
const aItem = aIndexed[authorId];
if (aItem) a[authorId] = Object.assign({}, aItem, bItem);
return a;
}, {}))
console.log(c);
Upvotes: 2