'Merge JavaScript objects in array with same key
What is the best way to merge array contents from JavaScript objects sharing a key in common?
How can array
in the example below be reorganized into output
? Here, all value
keys (whether an array or not) are merged into all objects sharing the same name
key.
var array = [
{
name: "foo1",
value: "val1"
}, {
name: "foo1",
value: [
"val2",
"val3"
]
}, {
name: "foo2",
value: "val4"
}
];
var output = [
{
name: "foo1",
value: [
"val1",
"val2",
"val3"
]
}, {
name: "foo2",
value: [
"val4"
]
}
];
Solution 1:[1]
Here is one option:-
var array = [{
name: "foo1",
value: "val1"
}, {
name: "foo1",
value: ["val2", "val3"]
}, {
name: "foo2",
value: "val4"
}];
var output = [];
array.forEach(function(item) {
var existing = output.filter(function(v, i) {
return v.name == item.name;
});
if (existing.length) {
var existingIndex = output.indexOf(existing[0]);
output[existingIndex].value = output[existingIndex].value.concat(item.value);
} else {
if (typeof item.value == 'string')
item.value = [item.value];
output.push(item);
}
});
console.dir(output);
Solution 2:[2]
Here is another way of achieving that goal:
var array = [{
name: "foo1",
value: "val1"
}, {
name: "foo1",
value: [
"val2",
"val3"
]
}, {
name: "foo2",
value: "val4"
}];
var output = array.reduce(function(o, cur) {
// Get the index of the key-value pair.
var occurs = o.reduce(function(n, item, i) {
return (item.name === cur.name) ? i : n;
}, -1);
// If the name is found,
if (occurs >= 0) {
// append the current value to its list of values.
o[occurs].value = o[occurs].value.concat(cur.value);
// Otherwise,
} else {
// add the current item to o (but make sure the value is an array).
var obj = {
name: cur.name,
value: [cur.value]
};
o = o.concat([obj]);
}
return o;
}, []);
console.log(output);
Solution 3:[3]
Using lodash
var array = [{name:"foo1",value:"val1"},{name:"foo1",value:["val2","val3"]},{name:"foo2",value:"val4"}];
function mergeNames (arr) {
return _.chain(arr).groupBy('name').mapValues(function (v) {
return _.chain(v).pluck('value').flattenDeep();
}).value();
}
console.log(mergeNames(array));
Solution 4:[4]
2021 version
- Using
reduce
to aggregate data. - Using
logical nullish assignment
only assigns ifacc[name]
is nullish (null or undefined
). - Using
Array.isArray
to determines whether the passed value is anArray
.
var arrays = [{ name: "foo1",value: "val1" }, {name: "foo1", value: ["val2", "val3"] }, {name: "foo2",value: "val4"}];
const result = arrays.reduce((acc, {name, value}) => {
acc[name] ??= {name: name, value: []};
if(Array.isArray(value)) // if it's array type then concat
acc[name].value = acc[name].value.concat(value);
else
acc[name].value.push(value);
return acc;
}, {});
console.log(Object.values(result));
Solution 5:[5]
Here is a version using an ES6 Map:
const arrays = [{ name: "foo1",value: "val1" }, {name: "foo1", value: ["val2", "val3"] }, {name: "foo2",value: "val4"}];
const map = new Map(arrays.map(({name, value}) => [name, { name, value: [] }]));
for (let {name, value} of arrays) map.get(name).value.push(...[value].flat());
console.log([...map.values()]);
Solution 6:[6]
Try this:
var array = [{name:"foo1",value:"val1"},{name:"foo1",value:["val2","val3"]},{name:"foo2",value:"val4"},{name:"foo2",value:"val5"}];
for(var j=0;j<array.length;j++){
var current = array[j];
for(var i=j+1;i<array.length;i++){
if(current.name = array[i].name){
if(!isArray(current.value))
current.value = [ current.value ];
if(isArray(array[i].value))
for(var v=0;v<array[i].value.length;v++)
current.value.push(array[i].value[v]);
else
current.value.push(array[i].value);
array.splice(i,1);
i++;
}
}
}
function isArray(myArray) {
return myArray.constructor.toString().indexOf("Array") > -1;
}
document.write(JSON.stringify(array));
Solution 7:[7]
Using reduce:
var mergedObj = array.reduce((acc, obj) => {
if (acc[obj.name]) {
acc[obj.name].value = acc[obj.name].value.isArray ?
acc[obj.name].value.concat(obj.value) :
[acc[obj.name].value].concat(obj.value);
} else {
acc[obj.name] = obj;
}
return acc;
}, {});
let output = [];
for (let prop in mergedObj) {
output.push(mergedObj[prop])
}
Solution 8:[8]
This work too !
var array = [
{
name: "foo1",
value: "val1",
},
{
name: "foo1",
value: ["val2", "val3"],
},
{
name: "foo2",
value: "val4",
},
];
let arr2 = [];
array.forEach((element) => { // remove duplicate name
let match = arr2.find((r) => r.name == element.name);
if (match) {
} else {
arr2.push({ name: element.name, value: [] });
}
});
arr2.map((item) => {
array.map((e) => {
if (e.name == item.name) {
if (typeof e.value == "object") { //lets map if value is an object
e.value.map((z) => {
item.value.push(z);
});
} else {
item.value.push(e.value);
}
}
});
});
console.log(arr2);
Solution 9:[9]
Use lodash "uniqWith". As shown below
let _ = require("lodash");
var array = [
{ name: "foo1", value: "1" },
{ name: "foo1", value: "2" },
{ name: "foo2", value: "3" },
{ name: "foo1", value: "4" }
];
let merged = _.uniqWith(array, (pre, cur) => {
if (pre.name == cur.name) {
cur.value = cur.value + "," + pre.value;
return true;
}
return false;
});
console.log(merged);
// output: [{ name: "foo1", value: "1,2,4" }, { name: "foo2", value: "3" }];
Solution 10:[10]
const exampleObj = [{
year: 2016,
abd: 123
}, {
year: 2016,
abdc: 123
}, {
year: 2017,
abdcxc: 123
}, {
year: 2017,
abdcxcx: 123
}];
const listOfYears = [];
const finalObj = [];
exampleObj.map(sample => {
listOfYears.push(sample.year);
});
const uniqueList = [...new Set(listOfYears)];
uniqueList.map(list => {
finalObj.push({
year: list
});
});
exampleObj.map(sample => {
const sampleYear = sample.year;
finalObj.map((obj, index) => {
if (obj.year === sampleYear) {
finalObj[index] = Object.assign(sample, obj);
}
});
});
The final object be [{"year":2016,"abdc":123,"abd":123},{"year":2017,"abdcxcx":123,"abdcxc":123}]
Solution 11:[11]
It's been a while since this question was asked, but I thought I'd chime in as well. For functions like this that execute a basic function you'll want to use over and over, I prefer to avoid longer-written functions and loops if I can help it and develop the function as a one-liner using shallow Array.prototype functions like .map()
and some other ES6+ goodies like Object.entries()
and Object.fromEntries()
. Combining all these, we can execute a function like this relatively easily.
First, I take in however many objects you pass to the function as a rest parameter and prepend that with an empty object we'll use to collect all the keys and values.
[{}, ...objs]
Next, I use the .map()
Array prototype function paired with Object.entries()
to loop through all the entries of each object, and any sub-array elements each contains and then either set the empty object's key to that value if it has not yet been declared, or I push the new values to the object key if it has been declared.
[{},...objs].map((e,i,a) => i ? Object.entries(e).map(f => (a[0][f[0]] ? a[0][f[0]].push(...([f[1]].flat())) : (a[0][f[0]] = [f[1]].flat()))) : e)[0]
Finally, to replace any single-element-arrays with their contained value, I run another .map()
function on the result array using both Object.entries()
and Object.fromEntries()
, similar to how we did before.
let getMergedObjs = (...objs) => Object.fromEntries(Object.entries([{},...objs].map((e,i,a) => i ? Object.entries(e).map(f => (a[0][f[0]] ? a[0][f[0]].push(...([f[1]].flat())) : (a[0][f[0]] = [f[1]].flat()))) : e)[0]).map(e => e.map((f,i) => i ? (f.length > 1 ? f : f[0]) : f)));
This will leave you with the final merged object, exactly as you prescribed it.
let a = {
a: [1,9],
b: 1,
c: 1
}
let b = {
a: 2,
b: 2
}
let c = {
b: 3,
c: 3,
d: 5
}
let getMergedObjs = (...objs) => Object.fromEntries(Object.entries([{},...objs].map((e,i,a) => i ? Object.entries(e).map(f => (a[0][f[0]] ? a[0][f[0]].push(...([f[1]].flat())) : (a[0][f[0]] = [f[1]].flat()))) : e)[0]).map(e => e.map((f,i) => i ? (f.length > 1 ? f : f[0]) : f)));
getMergedObjs(a,b,c); // { a: [ 1, 9, 2 ], b: [ 1, 2, 3 ], c: [ 1, 3 ], d: 5 }
Solution 12:[12]
try this :
var array = [
{
name: "foo1",
value: "val1"
}, {
name: "foo1",
value: [
"val2",
"val3"
]
}, {
name: "foo2",
value: "val4"
}
];
var output = [
{
name: "foo1",
value: [
"val1",
"val2",
"val3"
]
}, {
name: "foo2",
value: [
"val4"
]
}
];
bb = Object.assign( {}, array, output );
console.log(bb) ;
Solution 13:[13]
const array = [{ name: "foo1", value: "val1" }, {name: "foo1", value: ["val2", "val3"] }, {name: "foo2", value: "val4"}];
const start = array.reduce((object, {name}) => ({...object, [name]: []}), {});
const result = array.reduce((object, {name, value}) => ({...object, [name]: [object[name], [value]].flat(2)}), start);
const output = Object.entries(result).map(([name, value]) => ({name: name, value: value}));
console.log(output);
Solution 14:[14]
A much more easier approach is this 2022:
var array = [
{
name: "foo1",
value: "val1"
}, {
name: "foo1",
value: [
"val2",
"val3"
]
}, {
name: "foo2",
value: "val4"
}
];
var output = [
{
name: "foo1",
value: [
"val1",
"val2",
"val3"
]
},
{
name: "foo2",
value: [
"val4"
]
}
];
function mergeBasedOnKey(list){
let c = Object.values(list.reduce((a, b) => {
a[b.name] = a[b.name] || {name: b.name, value: []}
if(typeof(b['value']) == "string"){
a[b.name].value.push(b['value'])
}
else{
a[b.name].value = [...a[b.name].value, ...b.value]
}
return a
}, {}))
return c
}
let ans = mergeBasedOnKey(array)
console.log(ans)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow