combining duplicate key's values in JavaScript array - javascript

I have an Array that contain some keys/values one of the values is an array I want combining the value of array from all recorded that have same key in my Array.
Below is an Simple Example to demonstrate, I am not able to construct its logic so seeking help in building a logic to it.
[{"somekey":"Some Value Pushed"},{"somekey":"Second Value"}]
I want Result Like,
[{"somekey":["Some Value Pushed","Second Value"]}]

The reduce() function of Array Object in JavaScript can merge any array into a single Object.
I wrote a single-line code to solve this problem.
I updated result with the array.
const arr = [{
somekey: "Some Value Pushed",
},
{
somekey2: "Second Value2",
},
{
somekey: "Some Value Pushed",
},
{
somekey2: "Second Value3",
},
{
somekey3: "",
},
{},
];
const ansObj = arr.reduce(
(prv, cur) => {
Object.entries(cur).forEach(([key, v]) => key in prv ? prv[key].push(v) : (prv[key] = [v]));
return prv;
}, {}
)
const ansArray = Object.entries(ansObj).map(([key, value])=>({[key]:value}));
console.log(ansArray);

You can try something like this:
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);
}
});
Or, another option using Lodash
function mergeNames (arr) {
return _.chain(arr).groupBy('name').mapValues(function (v) {
return _.chain(v).pluck('value').flattenDeep();
}).value();
}

Maybe something like:
const data = [
{"somekey":"Some Value Pushed"},
{"somekey":"Second Value", "otherkey": 1},
{"otherkey": 2}
];
const merge_and_group = (obj1, obj2) =>
Object.entries(obj2).reduce(
(acc, [key, val]) => {
acc[key] ??= [];
acc[key].push(val);
return acc;
},
obj1
);
const res = data.reduce(merge_and_group, {});
console.log(res);

const arr = [{
"somekey": "Some Value Pushed"
}, {
"somekey2": "Second Value2"
}, {
"somekey": "Some Value Pushed"
}, {
"somekey2": "Second Value3"
}]
const newarr = {}
arr.forEach(obj => {
for (const [key, value] of Object.entries(obj)) {
if (newarr[key]) newarr[key].push(value)
else newarr[key] = [value]
}
})
console.log(newarr)

Array.prototype.reduce() is a possible option.
the reduce() method executes a reducer function which is provided as an input on each element of the array and returning a single output value.
const array = [{"somekey":"Some Value Pushed"},{"somekey":"Second Value"}];
const res = array.reduce((acc, el) => {
const [key, value] = Object.entries(el)[0];
(acc[key] || (acc[key] = [])).push(value);
return acc;
}, {});
console.log(res)

Assuming each element of your array is an object with a single key.
const array = [
{ somekey: "Some Value Pushed" },
{ somekey: "Second Value" },
{ foo: "bar" },
{ foo: "baz" },
{ somekey: "Third Value" },
];
const result = [];
array.forEach(el => {
let [key, value] = Object.entries(el)[0];
for (let el of result) if (key in el) {
el[key].push(value);
return;
}
result.push({ [key]: [value] });
});
console.dir(result);

If your array has only "somekey" as keys then you can use map method as following:
const array = [{"somekey":"Some Value Pushed"},{"somekey":"Second Value"}];
const valuesArray = array.map(obj => obj.somekey);
result = [{"somekey":valuesArray}];
console.log(result)

If your array has other keys along with "somekey" and you like to separate values corresponding to only "somekey" then try the following:
const array = [{"somekey":"Some Value Pushed"},{"somekey":"Second Value"}, {"otherkey":"other Value"}];
const filteredArray = array.filter((obj) => {
return "somekey" in obj
}, []);
const valuesArray = filteredArray.map(obj => obj.somekey);
result = [{"somekey":valuesArray}];
console.log(result)

Related

Merge Objects in a array and increase count

[{name:"abc",value:5},{name:"abc",value:10},{name:"abc1",value:5},{name:"abc1",value:15}]
I want to merge it by name so that the new array will be
[{name:"abc",value:15},{name:"abc1",value:20}]
Can i do it with es6 or a simple function
Using reduce and without find or findIndex
const data = [{name:"abc",value:5},{name:"abc",value:10},{name:"abc1",value:5},{name:"abc1",value:15}];
const summedDataObj = data.reduce((acc, entry) => {
if (acc[entry.name]) acc[entry.name].value += entry.value;
else acc[entry.name] = entry;
return acc;
}, {});
const summedDataArr = Object.values(summedDataObj);
console.log(summedDataArr);
We can do it via Array.reduce()
let data = [{name:"abc",value:5},{name:"abc",value:10},{name:"abc1",value:5},{name:"abc1",value:15}]
let result = data.reduce((a,{name,value}) => {
let obj = a.find(e => e.name === name)
if(obj){
obj.value += value
}else{
a.push({name,value})
}
return a
},[])
console.log(result)
you can group the data by name using this function:
function groupBy(arr, prop) {
const map = new Map(Array.from(arr, obj => [obj[prop], []]));
arr.forEach(obj => map.get(obj[prop]).push(obj));
return Array.from(map.values());
}
this yields this result:
[
[
{"name": "abc", "value": 5},
{"name": "abc", "value": 10}
],
[
{"name": "abc1", "value": 5},
{"name": "abc1", "value": 15}
]
]
which can be aggregated by using reduce on each resulting array:
groupedData.map(entry=>entry.reduce((acc,cur)=>({
...acc,
value: acc.value + cur.value
})))
so all together we get:
function groupBy(arr, prop) {
const map = new Map(Array.from(arr, obj => [obj[prop], []]));
arr.forEach(obj => map.get(obj[prop]).push(obj));
return Array.from(map.values());
}
const data = [{name:"abc",value:5},{name:"abc",value:10},{name:"abc1",value:5},{name:"abc1",value:15}]
const aggregatedData = groupBy(data,"name")
.map(entry=>entry.reduce((acc,cur)=>({
...acc,
value:acc.value+cur.value
})))
const obj = [{name:"abc",value:5},{name:"abc",value:10},{name:"abc1",value:5},{name:"abc1",value:15}]
arr = obj.reduce((obj, item) => {
let find = obj.find(i => i.name === item.name && i.date === item.date);
let _d = {
...item
}
find ? (find.value += item.value ) : obj.push(_d);
return obj;
}, [])
console.log(arr);
const data = [{
name: "abc",
value: 5
},
{
name: "abc",
value: 10
},
{
name: "abc1",
value: 5
},
{
name: "abc1",
value: 15
},
];
const groupArr = data.reduce((r, a) => {
const idx = r.findIndex((el) => el.name === a.name);
idx === -1 ? r.push(a) : (r[idx].value += a.value);
return r;
}, []);
console.log(groupArr);
You can achieve this with the help of Array#reduce method.
Live Demo :
const arr = [{name:"abc",value:5},{name:"abc",value:10},{name:"abc1",value:5},{name:"abc1",value:15}];
const res = arr.reduce((obj, curr) => {
if (obj.hasOwnProperty(curr.name) && obj[curr.name].name === curr.name) {
obj[curr.name].value += curr.value
} else {
obj[curr.name] = curr;
}
return obj
}, {});
console.log(Object.values(res));

How do I convert Array of object key value to Object in object key value

I'm trying to reduce and array of object(key-value) to one object(key-value).
I'm working with a json object injavascript
I have this:
"relations":
[
{"609e598ad32e90519043f09f": "609d8b78cf7bb100045fc593"},
{"609e5945d32e90519043f09e": "609d8b78cf7bb100045fc593"},
{"609e58b496d43235884e788f": "609d8b78cf7bb100045fc593"},
{"609e57e3e1560e58245544f4": "609d8de9575b49000466e358"}
],
And below is my expected result:
"relations":
{
"609e598ad32e90519043f09f":"609d8b78cf7bb100045fc593",
"609e5945d32e90519043f09e":"609d8b78cf7bb100045fc593",
"609e58b496d43235884e788f":"609d8b78cf7bb100045fc593",
"609e57e3e1560e58245544f4":"609d8de9575b49000466e358"
}
How can I achieve this result?
You can use for..in loop to get the key-value and using reduce to store it into an object. Then you can assign it to relations property.
let obj = {
relations: [
{ "609e598ad32e90519043f09f": "609d8b78cf7bb100045fc593" },
{ "609e5945d32e90519043f09e": "609d8b78cf7bb100045fc593" },
{ "609e58b496d43235884e788f": "609d8b78cf7bb100045fc593" },
{ "609e57e3e1560e58245544f4": "609d8de9575b49000466e358" },
],
};
const result = obj.relations.reduce((acc, curr) => {
for (let key in curr) {
acc[key] = curr[key];
}
return acc;
}, {});
obj.relations = result;
console.log(obj);
You can also clone the object and change only the relations key using object destructuring
let obj = {
name: "test",
relations: [
{ "609e598ad32e90519043f09f": "609d8b78cf7bb100045fc593" },
{ "609e5945d32e90519043f09e": "609d8b78cf7bb100045fc593" },
{ "609e58b496d43235884e788f": "609d8b78cf7bb100045fc593" },
{ "609e57e3e1560e58245544f4": "609d8de9575b49000466e358" },
],
};
const result = obj.relations.reduce((acc, curr) => {
for (let key in curr) {
acc[key] = curr[key];
}
return acc;
}, {});
const cloneWithChanges = { ...obj, relations: result };
console.log(cloneWithChanges);

Rebuilding/Parsing plain JavaScript object

Let's say I have an object containing objects that have 30 key-value pairs each:
const data = {
"foo": {
"3/16/21": 'buzz',
"3/17/21": 'fizz',
...
"4/13/21": 'lorem',
"4/14/21": 'ipsum'
},
"bar": {
"3/16/21": 'sit',
"3/17/21": 'amet',
...
"4/13/21": 'dummy',
"4/14/21": 'text'
},
};
My goal is to rebuild this object into something like this:
myData = [
{date: "3/16/21", foo: 'buzz', bar : 'sit'}
{date: "3/17/21", foo: 'fizz', bar : 'amet'} ,
...
{date: "4/13/21", foo: 'lorem', bar : 'dummy'}
{date: "4/14/21", foo: 'ipsum', bar : 'text'}
];
The function below works like charm but I feel like there is a 10x better way to do it. I would love to see your suggestions on how I could improve it.
const processAPIdata = (data) => {
if (data) {
var myData = [];
for (var key in data) {
if (!data.hasOwnProperty(key)) continue;
var obj = data[key];
for (var prop in obj) {
if (!obj.hasOwnProperty(prop)) continue;
if (myData.length < 30) {
myData.push({ date: prop });
}
let pos = myData.map(function (e) { return e.date; }).indexOf(prop);
myData[pos][key] = obj[prop];
}
}
}
return myData;
};
I'd group into an object indexed by date. When iterating, create the object for that date if it doesn't exist yet, with { date } (where date is the inner property being iterated over), and assign a new property from the outer key (for the new key) and the inner value (for the new value):
const data = {
"foo": {
"3/16/21": 'buzz',
"3/17/21": 'fizz',
"4/13/21": 'lorem',
"4/14/21": 'ipsum'
},
"bar": {
"3/16/21": 'sit',
"3/17/21": 'amet',
"4/13/21": 'dummy',
"4/14/21": 'text'
},
};
const newDataByDate = {};
for (const [key, obj] of Object.entries(data)) {
for (const [date, val] of Object.entries(obj)) {
newDataByDate[date] ??= { date };
newDataByDate[date][key] = val;
}
}
console.log(Object.values(newDataByDate));
You can complete by doing this simple way.
const data = {"foo":{"3/16/21":'buzz',"3/17/21":'fizz',"4/13/21":'lorem',"4/14/21":'ipsum'},"bar":{"3/16/21":'sit',"3/17/21":'amet',"4/13/21":'dummy',"4/14/21":'text'},};
const result = Object.entries(data).reduce((acc, [key, values]) => {
for(const [date, v] of Object.entries(values)){
acc[date] = acc[date] || {date}
acc[date][[key]] = v;
}
return acc;
}, {});
console.log(Object.values(result));
We can achieve this using Object.entries, Array.reduce & Object.values like below
const data = {"foo":{"3/16/21":'buzz',"3/17/21":'fizz',"4/13/21":'lorem',"4/14/21":'ipsum'},"bar":{"3/16/21":'sit',"3/17/21":'amet',"4/13/21":'dummy',"4/14/21":'text'}};
const formatData = (data) => {
//Convert the object to array of arrays with value at first index being the keys and value at second index being values
const result = Object.entries(data).reduce((acc, [key, val]) => {
//Since we have object as value, we need to again convert to array of arrays in order to get the date and the corresponding value
Object.entries(val).forEach(([date, innerVal]) => {
//update the accumulator with new key-value
acc[date] = {
...(acc[date] || {date}),
[key]: innerVal
}
})
return acc;
}, {});
//Return the values of the accumulator
return Object.values(result);
}
console.log(formatData(data));
.as-console-wrapper {
max-height: 100% !important;
}

Merging two objects with same keys override first array

I would like to merge an array with another array. The only catch is that each array is within an object.
Intuitively I tried {...arrObj, ...newArrObj} however this leads newArrObj overwriting items in the arrObj.
const array = ['an', 'array'];
const newArray = [, , 'new', 'ehrray'];
const obj = {
key: { ...array
}
};
const newObj = {
key: { ...newArray
}
};
const merged = { ...obj,
...newObj
};
console.log(merged);
I would expect merged to be:
{
"key": {
"0": "an",
"1": "array",
"2": "new",
"3": "ehrray"
}
}
but receive
{
"key": {
"2": "new",
"3": "ehrray"
}
}
This might be useful
const a0 = ['1', '2', undefined , undefined, '5', '6', '7'];
const a1 = [undefined, undefined, '3', '4'];
function merge(a, b) {
return a.map(function(v,i){ return v?v:b[i]});
}
console.log(a0 > a1?merge(a0, a1):merge(a1, a0));
I wanted to updated that I ended up going with a recursive merge to get the nested object containing an array merged.
const array = ['an', 'array'];
const newArray = [, , 'new', 'ehrray'];
const obj = {
key: { ...array
}
};
const newObj = {
key: { ...newArray
}
};
const merge = (obj1, obj2) => {
const recursiveMerge = (obj, entries) => {
for (const [key, value] of entries) {
if (typeof value === "object") {
obj[key] = obj[key] ? { ...obj[key]
} : {};
recursiveMerge(obj[key], Object.entries(value))
} else {
obj[key] = value;
}
}
return obj;
}
return recursiveMerge(obj1, Object.entries(obj2))
}
console.log(merge(obj, newObj));
The idea is that there are unset values with only a few set. eg. const newArray = new Array(4); newArray[2] = 'new';
{ value: null }, even { value: undefined } is not the same thing as { foo: 42 } with no value at all. That's the reason that in your example "an" and "array" are overwritten with the nulls from the newArray.
This particular example you can solve by swapping the order in which you add the arrays to the result, but as soon as both arrays contain null-values there is no way to do it with spread-syntax / Object.assign alone. You have to implement the behaviour:
const array = new Array('an', 'array', null, null, "and", "more", "from", "array");
const newArray = new Array(null, null, 'new', 'ehrray');
function merge(a, b) {
const result = [];
for (let i = 0; i < a.length || i < b.length; ++i) {
result[i] = b[i] == null ? a[i] : b[i];
}
return result;
}
console.log(merge(array, newArray));

JavaScript - build a tree data structure recursively

I have a function called tree, which takes array of objects (as data fields from a database) and array of strings for keys. The function loops through rowsArray and recursively creates object with nested properties based on keyArray.
const tree = (rowsArray, keysArray) => {
return rows.reduce((acc, row) => {
const groupBy = (row, keys,) => {
const [first, ...rest] = keys;
if (!first) return [row];
return {
[row[first]]: groupBy(row, rest),
}
};
acc = {...groupBy(row, keys), ...acc};
return acc;
}, {});
}
The data is following:
const data = [{
ID: 1,
Main: "Financial",
Sub: "Forecasts",
Detail: "General"
}, {
ID: 2,
Main: "Financial",
Sub: "HR",
Detail: "Headcount"
}];
const result1 = tree(data, ["Main", "Sub", "Detail"]);
console.log(result1);
When I log the result, I get:
/*
// actual output
{
Financial: {
Forecasts: {
General: [Array]
}
}
}
Whereas, I would like to get following:
// expected
{
Financial: {
Forecasts: {
General: [Array]
},
HR: {
Headcount: [Array]
}
}
}
*/
The problem is, that acc variable in main function gets overridden and I get new object, instead of accumulative and I am not quite sure how to recursively build this object. I tried to pass instances of acc to groupBy function (to remember previous results), but no luck.
Do you have any idea how I could rewrite tree function or groupBy function to accomplish my goal? Thanks!
You could do it like this:
function tree(rows, keys) {
return rows.reduce( (acc, row) => {
keys.reduce( (parent, key, i) =>
parent[row[key]] = parent[row[key]] || (i === keys.length - 1 ? [row] : {})
, acc);
return acc;
}, {});
}
const data = [{ID: 1,Main: "Financial",Sub: "Forecasts",Detail: "General"}, {ID: 2,Main: "Financial",Sub: "HR", Detail: "Headcount" }];
const result1 = tree(data, ["Main", "Sub", "Detail"]);
console.log(result1);
Be aware that the spread syntax makes a shallow copy. Instead, in this solution, the accumulator is passed to the inner reduce. And so we actually merge the new row's hierarchical data into the accumulator on-the-spot.
The problem is your merge function is not deep. When you assign the values to the accumulator you overwrite existing properties - in this case Financial.
I included a deep merge function from here and now it works.
I also fixed some reference errors you had:
rows => rowsArray
keys = keysArray
// deep merge function
function merge(current, update) {
Object.keys(update).forEach(function(key) {
// if update[key] exist, and it's not a string or array,
// we go in one level deeper
if (current.hasOwnProperty(key) &&
typeof current[key] === 'object' &&
!(current[key] instanceof Array)) {
merge(current[key], update[key]);
// if update[key] doesn't exist in current, or it's a string
// or array, then assign/overwrite current[key] to update[key]
} else {
current[key] = update[key];
}
});
return current;
}
const tree = (rowsArray, keysArray) => {
return rowsArray.reduce((acc, row) => {
const groupBy = (row, keys, ) => {
const [first, ...rest] = keys;
if (!first) return [row];
return {
[row[first]]: groupBy(row, rest),
}
};
acc = merge(groupBy(row, keysArray), acc);
return acc;
}, {});
}
const data = [{
ID: 1,
Main: "Financial",
Sub: "Forecasts",
Detail: "General"
}, {
ID: 2,
Main: "Financial",
Sub: "HR",
Detail: "Headcount"
}];
const result1 = tree(data, ["Main", "Sub", "Detail"]);
console.log(result1);
You could iterate the keys and take either an object for not the last key or an array for the last key and push then the data to the array.
const tree = (rowsArray, keysArray) => {
return rowsArray.reduce((acc, row) => {
keysArray
.map(k => row[k])
.reduce((o, k, i, { length }) => o[k] = o[k] || (i + 1 === length ? []: {}), acc)
.push(row);
return acc;
}, {});
}
const data = [{ ID: 1, Main: "Financial", Sub: "Forecasts", Detail: "General" }, { ID: 2, Main: "Financial", Sub: "HR", Detail: "Headcount" }];
const result1 = tree(data, ["Main", "Sub", "Detail"]);
console.log(result1);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can iterate over the data and created a unique key based on the keys provided and then recursively generate the output structure by deep cloning.
const data = [{
ID: 1,
Main: "Financial",
Sub: "Forecasts",
Detail: "General"
}, {
ID: 2,
Main: "Financial",
Sub: "HR",
Detail: "Headcount"
}];
function generateKey(keys,json){
return keys.reduce(function(o,i){
o += json[i] + "_";
return o;
},'');
}
function merge(first,second){
for(var i in second){
if(!first.hasOwnProperty(i)){
first[i] = second[i];
}else{
first[i] = merge(first[i],second[i]);
}
}
return first;
}
function generateTree(input,keys){
let values = input.reduce(function(o,i){
var key = generateKey(keys,i);
if(!o.hasOwnProperty(key)){
o[key] = [];
}
o[key].push(i);
return o;
},{});
return Object.keys(values).reduce(function(o,i){
var valueKeys = i.split('_');
var oo = {};
for(var index = valueKeys.length -2; index >=0 ;index--){
var out = {};
if(index === valueKeys.length -2){
out[valueKeys[index]] = values[i];
}else{
out[valueKeys[index]] = oo;
}
oo = out;
}
o = merge(o,oo);
return o;
},{});
}
console.log(generateTree(data,["Main", "Sub", "Detail"]));
jsFiddle Demo - https://jsfiddle.net/6jots8Lc/

Categories