Reputation: 610
I have two arrays, below, which I'd like to merge based on two common keys ("iso3" and "year"). Each object must have both in common in order to be merged. The arrays are not the same length.
array1 = [{
"id":24006,
"iso3":"AFG",
"country":"Afghanistan",
"year":2014,
"value":29.78
},
{
"id":138806,
"iso3":"ALB",
"country":"Albania",
"year":2013,
"value":0.6341109715
},
{
"id":44206,
"iso3":"DZA",
"country":"Algeria",
"year":2014,
"value":39.928947
}]
array2 = [{
"indicator_id":21806,
"footnote_id":64811,
"iso3":"AFG",
"year":2014
},
{
"indicator_id":23806,
"footnote_id":15711,
"iso3":"AFG",
"year":2013
},
{
"indicator_id":123406,
"footnote_id":15711,
"iso3":"ALB",
"year":2013
},
{
"indicator_id":101606,
"footnote_id":48911,
"iso3":"DZA",
"year":2013
}];
I saw this answer, but the arrays are merged based on only one common key. I tried to work from that code, but I didn't get anywhere. Any help is appreciated!
Upvotes: 1
Views: 693
Reputation: 386578
This solution features an object for collecting items and renders then the wanted array.
var array1 = [{ "id": 24006, "iso3": "AFG", "country": "Afghanistan", "year": 2014, "value": 29.78 }, { "id": 138806, "iso3": "ALB", "country": "Albania", "year": 2013, "value": 0.6341109715 }, { "id": 44206, "iso3": "DZA", "country": "Algeria", "year": 2014, "value": 39.928947 }],
array2 = [{ "indicator_id": 21806, "footnote_id": 64811, "iso3": "AFG", "year": 2014 }, { "indicator_id": 23806, "footnote_id": 15711, "iso3": "AFG", "year": 2013 }, { "indicator_id": 123406, "footnote_id": 15711, "iso3": "ALB", "year": 2013 }, { "indicator_id": 101606, "footnote_id": 48911, "iso3": "DZA", "year": 2013 }];
function merge(array1, array2) {
function makeObj(a) {
obj[a.iso3] = obj[a.iso3] || {};
obj[a.iso3][a.year] = obj[a.iso3][a.year] || {};
Object.keys(a).forEach(function (k) {
obj[a.iso3][a.year][k] = a[k];
});
}
var array = [],
obj = {};
array1.forEach(makeObj);
array2.forEach(makeObj);
Object.keys(obj).forEach(function (k) {
Object.keys(obj[k]).forEach(function (kk) {
array.push(obj[k][kk]);
});
});
return array;
}
document.write('<pre>' + JSON.stringify(merge(array1, array2), 0, 4) + '</pre>');
Upvotes: 0
Reputation: 752
This approach first creates a dictionary (or map) of the merged objects based on the key. It uses jQuery $.extend to merge the individual objects (other utility libraries will likely provide a means to merge objects)
After producing the dictionary it then transform back into an array and filters by individual object keys to produce an inner join like result. Simply remove the filter if you are after an outer join.
var array1 = [{
"id":24006,
"iso3":"AFG",
"country":"Afghanistan",
"year":2014,
"value":29.78
},
{
"id":138806,
"iso3":"ALB",
"country":"Albania",
"year":2013,
"value":0.6341109715
},
{
"id":44206,
"iso3":"DZA",
"country":"Algeria",
"year":2014,
"value":39.928947
}]
var array2 = [{
"indicator_id":21806,
"footnote_id":64811,
"iso3":"AFG",
"year":2014
},
{
"indicator_id":23806,
"footnote_id":15711,
"iso3":"AFG",
"year":2013
},
{
"indicator_id":123406,
"footnote_id":15711,
"iso3":"ALB",
"year":2013
},
{
"indicator_id":101606,
"footnote_id":48911,
"iso3":"DZA",
"year":2013
}];
// merge by key where possible
var dict = array1.concat(array2).reduce(function (dict, val) {
// Use the two values we want to join on as a key
var key = val.iso3 + val.year;
// Lookup existing object with same key, default to
// empty object if one does not exist
var existing = dict[key] || {};
// Merge two objects
dict[key] = $.extend(existing, val);
return dict;
}, {});
var joined = Object.keys(dict).map(function (key) {
// pull the dictionary values back out into an array
return dict[key];
}).filter(function (obj) {
// this is making it work like an inner out, an outer join can be
// done by removing the filter
return (typeof obj.indicator_id !== "undefined") && (typeof obj.country !== "undefined");
});
JS Fiddle (output is logged to console: http://jsfiddle.net/9v47zLy6/)
Upvotes: 0
Reputation: 35950
Another approach that utilizes sort()
and reduce()
.
Code:
/* Merges two objects into one. You might add some custom logic here,
if you need to handle some more complex merges, i.e. if the same
key appears in merged objects in both arrays */
var merge = function(o1, o2) {
var result = {};
for (var a in o1) { result[a] = o1[a]; }
for (var a in o2) { result[a] = o2[a]; }
return result;
}
/* Returns 0 if two objects are equal, -1 if first one is 'lower',
and 1 if it's 'larger'. It's used to sort the arrays. Objects are
compared using iso3 and year properties only. */
var compareByIsoAndYear = function (o1, o2) {
if (o1.iso3 === o2.iso3 && o1.year === o2.year) return 0
else if (o1.year > o2.year) return 1
else if (o1.year < o2.year) return -1
else if (o1.iso3 > o2.iso3) return 1;
}
/* Used in reduce */
var mergeTwoObjects = function(initial, current) {
if (initial.length > 0) {
var last = initial[initial.length-1];
if (compareByIsoAndYear(last, current) === 0) {
initial[initial.length-1] = merge(last, current);
return initial;
}
}
initial.push(current);
return initial;
}
/* Take both arrays and concatenate them into one. Then sort them,
to make sure that objects with the same year and iso3 would appear
next to each other, and then merge them if needed */
var result = array1.concat(array2)
.sort(compareByIsoAndYear)
.reduce(mergeTwoObjects, []);
Result:
[{
"indicator_id": 23806, /* Found in array2 only, not merged */
"footnote_id": 15711,
"iso3": "AFG",
"year": 2013
}, {
"id": 138806, /* This one has been merged */
"iso3": "ALB",
"country": "Albania",
"year": 2013,
"value": 0.6341109715,
"indicator_id": 123406,
"footnote_id": 15711
}, {
"indicator_id": 101606, /* Found in array2 only, not merged */
"footnote_id": 48911,
"iso3": "DZA",
"year": 2013
}, {
"id": 24006, /* This one has been merged */
"iso3": "AFG",
"country": "Afghanistan",
"year": 2014,
"value": 29.78,
"indicator_id": 21806,
"footnote_id": 64811
}, {
"id": 44206, /* Found in array1 only, not merged */
"iso3": "DZA",
"country": "Algeria",
"year": 2014,
"value": 39.928947
}]
Upvotes: 3
Reputation: 466
What you need to do is extend the joining condition offered in the answer you sited. Like this:
var combined = [];
function findSecond(iso3, year, second){
for (var i = 0; i < second.length; i += 1){
//Adding the second condition
if (second[i].iso3 === iso3 && second[i].year === year ) {
return second[i];
}
}
return null;
}
while (el = array1.pop()){
var getSec = findSecond(el.iso3, el.year, array2);
if (getSec){
for (var l in getSec){
if (!(l in el)) {
el[l] = getSec[l];
}
}
combined.push(el);
}
}
Upvotes: 0