How to group and concat values by keys in JS - javascript

I try like a maniac to do some kind of a merge / sort by key on an array of object.
Don't know why but I cannot figure how to do that.
My app is in Ionic / Angular.
Here is what I have :
[
{
"2021": {
"a": "aText"
}
},
{
"2021": {
"b": "bText"
}
},
{
"2020": {
"z": "zText"
}
},
{
"2020": {
"y": "yText"
}
},
{
"2020": {
"x": "xText"
}
}
]
My goal is to get this :
[
{
"2021": { "a": "aText", "b": "bText" }
},
{
"2020": { "z": "zText", "y": "yText", "x": "xText" }
}
]
In other words, I'd like to regroup by year and concat them.
Does anybody have an idea on how to do that ?

If you just want a single object with years as keys then it's just a standard 'group by' with a nested loop to iterate the Object.entries() of each object. If you do want your originally posted output (an array of individual objects) you can just map() the entires of the returned grouped object and convert each to an object using Object.fromEntries()
const input = [
{ 2021: { a: 'aText' } },
{ 2021: { b: 'bText' } },
{ 2020: { z: 'zText' } },
{ 2020: { y: 'yText' } },
{ 2020: { x: 'xText' } },
];
const grouped_object = input.reduce(
(a, o) => (Object.entries(o).forEach(([y, o]) => (a[y] = { ...(a[y] ?? {}), ...o })), a),
{}
);
// if you just want a single object with years as keys
console.log(grouped_object);
const grouped_array = Object.entries(grouped_object)
.map(([year, data]) => ({[year]: data}));
// the output from your question
console.log(grouped_array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Or refactored to use a for...of loop and Object.assign()
const input = [
{ 2021: { a: 'aText' } },
{ 2021: { b: 'bText' } },
{ 2020: { z: 'zText' } },
{ 2020: { y: 'yText' } },
{ 2020: { x: 'xText' } },
];
const grouped_object = {};
for (const obj of input) {
for (const [year, data] of Object.entries(obj)) {
grouped_object[year] = Object.assign(grouped_object[year] ?? {}, data);
}
}
// if you just want a single object with years as keys
console.log(grouped_object);
// or avoiding computed properties
const grouped_array = Object.entries(grouped_object)
.map(([year, data]) => (o={}, o[year]=data, o));
// the output from your question
console.log(grouped_array);
.as-console-wrapper { max-height: 100% !important; top: 0; }

It would be better to use an object to group by year. This is an example that uses reduce to iterate over the array to produce that object.
const data=[{2021:{a:"aText"}},{2021:{b:"bText"}},{2020:{z:"zText"}},{2020:{y:"yText"}},{2020:{x:"xText"}}];
const out = data.reduce((acc, obj) => {
// Get the key and value from the object that in
// the current iteration
const [ [ key, value ] ] = Object.entries(obj);
// If the key doesn't exist on the accumulator (the initial
// object that we passed into the `reduce`) create an empty object
acc[key] = acc[key] || {};
// Update the value of that object property with
// the value of the object
acc[key] = { ...acc[key], ...value };
// Return the updated object for the next iteration
return acc;
// Here's the initial object that
// acts as the accumulator through all the iterations
}, {});
console.log(out);
Or using an array to hold the information for each year:
const data=[{2021:{a:"aText"}},{2021:{b:"bText"}},{2020:{z:"zText"}},{2020:{y:"yText"}},{2020:{x:"xText"}}];
const out = data.reduce((acc, obj) => {
const [ [ key, value ] ] = Object.entries(obj);
// Use an array instead of an object
acc[key] = acc[key] || [];
// Push the first element of the Object.values
// into the array
acc[key] = [ ...acc[key], Object.values(value)[0] ];
return acc;
}, {});
console.log(out);
Additional documentation
Object.entries
Object.values
Destructuring assignment
Spread syntax

Related

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;
}

How to flatten array in JS?

I have a data that is like following:
const data = [{
ratings: [ { rating: 5 } ],
counts: [ { count: 100 } ],
}];
And I want to flatten it in a sense that I want to get rid of arrays and have only objects, and end result to be:
const data = {
ratings: { rating: 5 },
counts: { count: 100 },
};
I tried to do something like this, but it is wrong and I believe I'm kind of over complicating it.
const flatten = data => {
return data.reduce((r, { ...children }) => {
Object.assign(children, r);
if (children) Object.assign(flatten(...Object.values(children)), r);
return r;
}, {})
}
Any ideas?
You could create recursive function with reduce method to turn all arrays to objects assuming you have just objects in those arrays.
const data = [{ratings: [ { rating: 5 } ],counts: [ { count: 100 } ]}];
function flatten(arr) {
return arr.reduce((r, e) => {
const obj = Object.assign({}, e);
for (let p in obj) {
if (Array.isArray(obj[p])) {
obj[p] = flatten(obj[p])
}
}
return Object.assign(r, obj)
}, {})
}
console.log(flatten(data))
If by any chance the data is result from JSON.parse :
var json = JSON.stringify( [{ratings:[{rating: 5}], counts:[{count: 100}]}] )
var result = JSON.parse(json, (k, v) => v[0] || v)
console.log( result )
Please check:
var data = [{ratings: [ { rating: 5 } ], counts: [ { count: 100 } ]}];
var flatten = function(data) {
if (Array.isArray(data)) {
data = data[0];
for (var key in data) data[key] = flatten(data[key]);
}
return data;
}
console.log(flatten(data));
Please check # CodePen
https://codepen.io/animatedcreativity/pen/842e17d2b9f83bc415513f937fc29be8

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/

lodash convert array of objects to single array of keys and multiple array of values

I need to transmit some data, that has too many key-value pairs.
As the keys are similar, I dont want to transmit them with each object.
Consider I have the following data:
[
{
x:11,
y:12
},{
x:21,
y:22
},{
x:31,
y:32
},{
x:41,
y:42
}
];
And I need the final output as
[ [x,y],[[11,12],[21,22],[31,32],[41,42]] ] OR
[ [x,y],[11,12],[21,22],[31,32],[41,42] ]
On the other end, I should be able to convert back to its original form.
It would be great if it can handle an additional key in some of the objects
I think I have seen lodash or underscore function for something close/similar to this, but I'm not able to find it right now.
NOTE: I don't know what the keys will be
Lodash v4.17.1
modify original
var modifiedOriginal = _.chain(original)
.map(_.keys)
.flatten()
.uniq()
.thru(function(header){
return _.concat(
[header],
_.map(original, function(item) {
return _.chain(item)
.defaults(_.zipObject(
header,
_.times(_.size(header), _.constant(undefined))
))
.pick(header)
.values()
.value()
})
);
})
.value();
modified back to original (keys order is not
guarantee)
var backToOriginal = _.map(_.tail(modified), function(item) {
return _.chain(_.head(modified))
.zipObject(item)
.transform(function(result, val, key) {
if (!_.isUndefined(val)) {
result[key] = val;
}
})
.value();
});
JSFiddle code https://jsfiddle.net/wa8kaL5g/1/
Using Array#reduce
var arr = [{
x: 11,
y: 12
}, {
x: 21,
y: 22
}, {
x: 31,
y: 32
}, {
x: 41,
y: 42
}];
var keys = Object.keys(arr[0]);
var op = arr.reduce(function(a, b) {
var arr = keys.reduce(function(x, y) {
return x.concat([b[y]]);
}, [])
return a.concat([arr]);
}, [keys]); //If all the objects are having identical keys!
console.log(JSON.stringify(op));
A little more verbose way of doing it:
[Edit: added the function to convert it back]
function convert(arr) {
var retArr = [ [/* keys (retArr[0]) */], [/* values (retArr[1]) */] ]
arr.forEach(function(obj){
// create new array for new sets of values
retArr[1].push([])
// put all of the keys in the correct array
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
// does the key exist in the array yet?
if (retArr[0].indexOf(key) === -1) {
retArr[0].push(key)
}
// get last index of retArr[1] and push on the values
retArr[1][retArr[1].length - 1].push(obj[key])
}
}
})
return retArr
}
function reConvert(arr) {
var retArr = []
var keys = arr[0]
arr[1].forEach(function(itemArr){
var obj = {}
itemArr.forEach(function(item, i){
obj[keys[i]] = item
})
retArr.push(obj)
})
return retArr
}
var objArr = [
{
x:11,
y:12
},{
x:21,
y:22
},{
x:31,
y:32
},{
x:41,
y:42
}
]
var arrFromObj = convert(objArr)
var objFromArr = reConvert(arrFromObj)
console.log(arrFromObj)
console.log(objFromArr)
A solution using Underscore.
First work out what the keys are:
var keys = _.chain(data)
.map(_.keys)
.flatten()
.uniq()
.value();
Then map across the data to pick out the value for each key:
var result = [
keys,
_.map(data, item => _.map(keys, key => item[key]))
];
and back again:
var thereAndBackAgain = _.map(result[1], item => _.omit(_.object(result[0], item), _.isUndefined));
Lodash's version of object is zipObject and omit using a predicate is omitBy:
var thereAndBackAgain = _.map(result[1], item => _.omitBy(_.zipObject(result[0], item), _.isUndefined));
var data = [
{
x:11,
y:12,
aa: 9
},{
x:21,
y:22
},{
x:31,
y:32,
z: 0
},{
x:41,
y:42
}
];
var keys = _.chain(data)
.map(_.keys)
.flatten()
.uniq()
.value();
var result = [
keys,
_.map(data, item => _.map(keys, key => item[key]))
];
var thereAndBackAgain = _.map(result[1], item => _.omit(_.object(result[0], item), _.isUndefined));
console.log(result)
console.log(thereAndBackAgain)
<script src="
https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
In ES6 you can do it by reducing it with Object.values(), and Object.keys(). You can restore it using a combination of Array.prototype.map() and Array.prototype.reduce():
const convertStructure = (data) => data.reduce((s, item) => {
s[1].push(Object.values(item));
return s;
}, [Object.keys(data[0]), []]); // all objects should be the same, so we can take the keys from the 1st object
const restoreStructure = ([keys, data]) => data.map((item) => item.reduce((o, v, i) => {
o[keys[i]] = v;
return o;
}, {}));
const data = [{
x: 11,
y: 12
}, {
x: 21,
y: 22
}, {
x: 31,
y: 32
}, {
x: 41,
y: 42
}];
const convertedStructure = convertStructure(data);
console.log('convertedStructure:\n', convertedStructure);
const restoredStructure = restoreStructure(convertedStructure);
console.log('restoredStructure:\n', restoredStructure);

Categories