How to extract and group an array by key - javascript

I am building an Angular 9 app.
In this app I have a dynamically fetched JSON array. I need to take the JSON array and then extract and group based upon the keys. Example array:
const collection = [{
title: 'Product A',
price: 234,
cost: 234
}, {
title: 'Product B',
price: 100,
cost: 200
}, {
title: 'Product C',
price: 344,
cost: 55
}, {
title: 'Product D',
price: 222,
cost: 332
}];
I can mange to extract individual keys but I want to have a method that takes any JSON array and then extract and group per key.
This is my code for extracting individual keys. I had to hard code the key name (title).
groupByKey(array, key) {
return array.map(a => a.title);
}
This is what I want to transform the original JSON array to:
[{
header: "title",
rows: ["Product A", "Product B", "Product C", "Product D"]
}, {
header: "price",
rows: [234, 100, 344, 222]
}, {
header: "cost",
rows: [234, 200, 55, 332]
}]

You can perform a reduce operation on the array, using an object to store all the values of each key.
const collection = [{
title: 'Product A',
price: 234,
cost: 234
}, {
title: 'Product B',
price: 100,
cost: 200
}, {
title: 'Product C',
price: 344,
cost: 55
}, {
title: 'Product D',
price: 222,
cost: 332
}];
function group(arr){
return Object.values(
arr.reduce((acc,curr)=>{
Object.entries(curr).forEach(([k,v])=>{
(acc[k] = acc[k] || {header: k, rows: []}).rows.push(v);
});
return acc;
}, {})
);
}
console.log(group(collection));

The simplest solution is:
const collection = [{
title: 'Product A',
price: 234,
cost: 234
}, {
title: 'Product B',
price: 100,
cost: 200
}, {
title: 'Product C',
price: 344,
cost: 55
}, {
title: 'Product D',
price: 222,
cost: 332
}];
const transform = inputArray => {
const headers = inputArray && inputArray[0] && Object.keys(inputArray[0]);
return headers.map(header =>
({header: header, rows: collection.map(item => item[header])}));
}
console.log(transform(collection));
Out:=>
[
{
header: 'title',
rows: [ 'Product A', 'Product B', 'Product C', 'Product D' ]
},
{
header: 'price',
rows: [ 234, 100, 344, 222 ]
},
{
header: 'cost',
rows: [ 234, 200, 55, 332 ]
}
]

You could do it non functional
const collection = [{
title: 'Product A',
price: 234,
cost: 234
}, {
title: 'Product B',
price: 100,
cost: 200
}, {
title: 'Product C',
price: 344,
cost: 55
}, {
title: 'Product D',
price: 222,
cost: 332
}]
const keys = Object.keys(collection[0]);
const arr = []
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const obj = {
header: null,
rows: []
};
for (let item of collection) {
obj['header'] = key;
obj['rows'].push(item[key])
}
arr.push(obj)
}
console.log(arr)

Approach using a Map, a simple loop and spread of Map#values()
const map = new Map(Object.keys(collection[0]).map(k => [k, {header:k, rows:[]}]));
collection.forEach(el =>Object.entries(el).forEach(([k, v]) => map.get(k).rows.push(v)))
console.log( [...map.values()])
<script>
const collection=[{title:"Product A",price:234,cost:234},{title:"Product B",price:100,cost:200},{title:"Product C",price:344,cost:55},{title:"Product D",price:222,cost:332}];
</script>

This function will simply take in the entire collection and does not need a key. It assumes that each object in the collection has the same keys.
const groupByKey = (collection) => {
const result = []
// return empty array if collection is empty
if (collections.length === 0) return result
// assumes that all json in the collection will have the same properties
const keys = Object.keys(collection[0])
keys.forEach(key => {
const row = []
collection.forEach(json => {
row.push(json[key])
})
const formattedJSON = {
header: key,
row: row
}
result.push(formattedJSON)
})
return result
}
As a note, in your groupByKey function, to get the key dynamically, you can do:
a[title]
because a.title will literally look for a key called "title".

Related

Set zero for missing data in array of objects

I have the following arrays of objects, for example:
const data = [
{
date: '01-01',
products: [
{
id: 1,
value: 6,
label: 'Product 1'
},
{
id: 2,
value: 3,
label: 'Product 2'
}
]
},
{
date: '02-01',
products: [
{
id: 1,
value: 4,
label: 'Product 1'
},
]
},
{
date: '03-01',
products: [
{
id: 1,
value: 11,
label: 'Product 1'
},
{
id: 2,
value: 15,
label: 'Product 2'
}
]
}
]
Then I do the grouping and get the following result:
const output = [
{
id: 1,
name: 'Product 1',
data: [6, 4, 11]
},
{
id: 2,
name: 'Product 2',
data: [3, 15]
}
]
The problem with the solution is that I cannot take into account the missing value (the object with the date "02-01" does not have an object with id: 2). I need to check that the object does not exist and substitute zero instead of the missing value. Maybe you know how to do it?
Solution code below:
const result = data.map(e => e.products).flat().reduce((acc, product) => {
const index = acc.findIndex(item => item.id === product.id);
if(index === -1) {
acc.push({
id: product.id,
name: product.label,
data: [product.value]
})
return acc;
}
const findIndex = acc[index].data.findIndex((innerNode) => innerNode.id === product.id);
if (findIndex === -1) {
console.log(product.value)
acc[index].data.push(product.value);
return acc;
}
return acc;
}, []);
Expected result:
const output = [
{
id: 1,
name: 'Product 1',
data: [6, 4, 11]
},
{
id: 2,
name: 'Product 2',
data: [3, 0, 15]
}
]
You can do this in three passes:
first, you find all dates. When you first encounter a product, you will set all its values to 0 for each of those dates.
then, you iterate products and ensure that, for each date, they have a value - which will be zero by default.
finally, you format the output.
const data = [
{
date: '01-01',
products: [
{
id: 1,
value: 6,
label: 'Product 1'
},
{
id: 2,
value: 3,
label: 'Product 2'
}
]
},
{
date: '02-01',
products: [
{
id: 1,
value: 4,
label: 'Product 1'
},
]
},
{
date: '03-01',
products: [
{
id: 1,
value: 11,
label: 'Product 1'
},
{
id: 2,
value: 15,
label: 'Product 2'
}
]
}
]
// goal is to fill this for each product
let dateToValues = data.map(d => [d.date, 0]);
// build map of product-id to values-for-each-date
let products = new Map();
data.forEach(d => d.products.forEach(p => {
let values = products.get(p.id)?.data;
if (values === undefined) {
values = new Map(dateToValues); // a copy
products.set(p.id, {label: p.label, data: values});
}
values.set(d.date, p.value);
}))
// generate output, skipping dates and only showing their values
let output = [];
products.forEach((v, id) => output.push({
id: id, name: v.label, data: [... v.data.values()]}));
console.log(output)

Returning array object by filtering it's nested size variants

I have the list of products that i want to filter by their varriants.size
Staring point, data I'm receiving:
const t1 = [
{
name: 'Product 1',
variants: [
{ size: 'sm', sku: '1' },
{ size: 'md', sku: '2' },
],
},
{
name: 'Product 2',
variants: [{ size: 'lg', sku: '4' }],
},
{
name: 'Product 3',
variants: [
{ size: 'sm', sku: '5' },
{ size: 'lg', sku: '6' },
],
},
{
name: 'Product 4',
variants: [{ size: 'sm', sku: '7' }],
},
]
By using ['sm', 'md'] I want to filter above object and return this result
End goal / expected results
const arr = [
{
name: 'Product 2',
variants: [{ size: 'lg', sku: '4' }],
},
{
name: 'Product 3',
variants: [{ size: 'lg', sku: '6' }],
},
]
What I've tried so far but not getting full data / missing properties.
const filter = ['sm', 'md']
const arr = t1.map((e) => {
const filter = e.variants.filter((f) => {
return filter.includes(f.size)
})
return filter
})
But only getting varriants object, rest of the data is missing.
This screenshot is bad example, this one is only filtering ['sm'] but in this case I have multiple filter option ['sm', 'md']
const
t1 = [
{ name: 'Product 1', variants: [{ size: 'sm', sku: '1' }, { size: 'md', sku: '2' }] },
{ name: 'Product 2', variants: [{ size: 'lg', sku: '4' }] },
{ name: 'Product 3', variants: [{ size: 'sm', sku: '5' }, { size: 'lg', sku: '6' }] },
{ name: 'Product 4', variants: [{ size: 'sm', sku: '7' }] }
],
filter = ['sm', 'md'];
const arr = t1
// filter t1 elements variants
.map(e => ({
...e,
variants: e.variants.filter(({ size }) => !filter.includes(size))
}))
// filter resulting elements with no variants left
.filter(({ variants }) => variants.length);
console.log(arr);
Filter each variants subarray by whether the size you want is included, then filter the whole t1 array by whether the subarray contains items.
const t1 = [
{
name: 'Product 1',
variants: [
{ size: 'sm', sku: '1' },
{ size: 'md', sku: '2' },
],
},
{
name: 'Product 2',
variants: [{ size: 'lg', sku: '4' }],
},
{
name: 'Product 3',
variants: [
{ size: 'sm', sku: '5' },
{ size: 'lg', sku: '6' },
],
},
{
name: 'Product 4',
variants: [{ size: 'sm', sku: '7' }],
},
];
const filterBy = ['sm', 'md'];
for (const obj of t1) {
obj.variants = obj.variants.filter(
subobj => !filterBy.includes(subobj.size)
);
}
const filteredInput = t1.filter(obj => obj.variants.length);
console.log(filteredInput);

Grouping and summing array objects

I'm having a data sample like this
this.userData = [
{id:1, category: 'Food', amount: 30, pDate: '2021-01-13', description: 'test desc'},
{id:2, category: 'Fuel', amount: 10, pDate: '2021-01-12', description: 'test desc'},
{id:3, category: 'Food', amount: 70, pDate: '2021-01-14', description: 'test desc'},
]
What I want to achieve with this data is to group it and sum it up so it comes out like this
[
{name: Food, total: 100},
{name: Fuel, total: 30}
]
What the current code I have, I do not get the output as I want.
const data = this.userData;
const groups = data.reduce((groups, item) => ({
...groups,
[item.category]: [...(groups[item.category] || []), item]
}), {});
console.log(groups);
You could take an object for grouping and get the values.
const
userData = [{ id:1, category: 'Food', amount: 30, pDate: '2021-01-13', description: 'test desc' }, { id:2, category: 'Fuel', amount: 10, pDate: '2021-01-12', description: 'test desc' }, { id:3, category: 'Food', amount: 70, pDate: '2021-01-14', description: 'test desc' }],
groups = Object.values(userData.reduce((r, o) => {
(r[o.category] ??= { name: o.category, total: 0 }).total += o.amount;
return r;
}, {}))
console.log(groups);
Try this
const userData = [
{id:1, category: 'Food', amount: 30, pDate: '2021-01-13', description: 'test desc'},
{id:2, category: 'Fuel', amount: 10, pDate: '2021-01-12', description: 'test desc'},
{id:3, category: 'Food', amount: 70, pDate: '2021-01-14', description: 'test desc'},
]
const hashMap = {}
for (const { category, amount } of userData) {
if (hashMap[category]) {
hashMap[category].total += amount
} else {
hashMap[category] = { name: category, total: amount }
}
}
const output = Object.values(hashMap)
console.log(output)

Restructure an array with lodash by leveraging _.map and ._groupBy

I am looking to restructure an array of objects with lodash.
I've been trying to adapt the many examples found online without any luck. It seems I would have to use a combination of _.map and ._groupBy but I can't really wrap my head around this.
Any help is appreciated!
Initial array:
const entries = [
{
year: '2019',
children: [
{ name: 'red', amount: 1, label: 'color' },
{ name: 'yellow', amount: 20, label: 'color' },
{ name: 'green', amount: 12, label: 'color' },
],
},
{
year: '2020',
children: [
{ name: 'red', amount: 1, label: 'color' },
{ name: 'yellow', amount: 3, label: 'color' },
],
},
]
Restructured array:
[
{
id: 'red',
data: [
{ year: '2019', amount: 1 },
{ year: '2020', amount: 1 },
],
},
{
id: 'yellow',
data: [
{ year: '2019', amount: 20 },
{ year: '2020', amount: 3 },
],
},
{
id: 'green',
data: [
{ year: '2019', amount: 12 },
],
},
]
You could chain the whole operations with flatMap, groupBy and mapping.
const entries = [{ year: '2019', children: [{ name: 'red', amount: 1, label: 'color' }, { name: 'yellow', amount: 20, label: 'color' }, { name: 'green', amount: 12, label: 'color' }] }, { year: '2020', children: [{ name: 'red', amount: 1, label: 'color' }, { name: 'yellow', amount: 3, label: 'color' }] }],
result = _(entries)
.flatMap(({ year, children }) => _.map(children, ({ name: id, amount }) => ({ year, id, amount })))
.groupBy('id')
.map((data, id) => ({ id, data: _.map(data, ({ year, amount }) => ({ year, amount })) }))
.value();
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
There are probably quite a few different ways of doing this, however, I find the best approach is:
Flatten the children to one array.
Use _.groupBy to create a map of these entries keyed on name.
Use _.entries to get an array of keys and values for the map.
Finally use _.map to transform these entries into our desired output.
const entries = [
{
year: '2019',
children: [
{ name: 'red', amount: 1, label: 'color' },
{ name: 'yellow', amount: 20, label: 'color' },
{ name: 'green', amount: 12, label: 'color' },
],
},
{
year: '2020',
children: [
{ name: 'red', amount: 1, label: 'color' },
{ name: 'yellow', amount: 3, label: 'color' },
],
},
]
// Step 1
let flattenedChildren = _.flatMap(entries, e => e.children.map(c => { return { ...c, year: e.year } }));
// Step 2
let entryMap = _.groupBy(flattenedChildren , "name");
// Step 3
let mapEntries = _.entries(entryMap);
// Step 4
let result = _.map(mapEntries , ([id, items]) => { return { id, data: items.map(item => _.pick(item, ["amount", "year"]))} });
console.log("Result:", result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

Build new array of objects with array containing unique data from array of objects with duplicate values of specific keys

I need your help...
I got an Array of Objects looking something like this:
var arr = [{
title: 'My title',
user: 1,
price: 22,
location: 'Berlin'
},{
title: 'My title',
user: 1,
price: 18,
location: 'Cologne'
},{
title: 'My title',
user: 1,
price: 26,
location: 'Hamburg'
},{
title: 'Other Title',
user: 2,
price: 26,
location: 'Frankfurt'
},{
title: 'Other Title',
user: 2,
price: 28,
location: 'Munich'
},];
Now I want to build a new Array of Objects that will look like this:
var result = [{
title: 'My title',
user: 1,
events: [
{
price: 22,
location: 'Berlin'
}, {
price: 18,
location: 'Cologne'
}, {
price: 26,
location: 'Hamburg'
}
]
},{
title: 'Other Title',
user: 2,
events: [
{
price: 28,
location: 'Munich'
},{
price: 26,
location: 'Frankfurt'
}
]
}];
I need to group the objects by multiple values, like in my example by user and title and add the unique data of them to a new field.
If someone could show me how to do that with lodash would be awesome!
Thank you for your help!
arr.reduce(function (hash, item) {
var key = item.title + item.user;
var obj = hash[key] || {};
obj.title = item.title;
obj.user = item.user;
obj.events = obj.events || [];
obj.events.push({
price: item.price,
location: item.location
});
hash[key] = obj;
return hash;
}, {});
var result = [];
for (var key in arr) {
result.push(arr[key]);
}
console.log(result); // the result array
Lodash answer:
function remap(arr) {
var out = _.reduce(arr, function(p, c) {
var key = [c.user, c.title].join('|');
p[key] = p[key] || { title: c.title, user: c.user, events: [] };
p[key].events.push({ price: c.price, location: c.location });
return p;
}, {});
return _.map(_.keys(out), function(el) {
return out[el];
});
}
remap(arr);
DEMO
This is a proposal in plain Javascript with a temporary object for the references to the result array.
var arr = [{ title: 'My title', user: 1, price: 22, location: 'Berlin' }, { title: 'My title', user: 1, price: 18, location: 'Cologne' }, { title: 'My title', user: 1, price: 26, location: 'Hamburg' }, { title: 'Other Title', user: 2, price: 26, location: 'Frankfurt' }, { title: 'Other Title', user: 2, price: 28, location: 'Munich' }],
grouped = function (array) {
var r = [], o = {};
array.forEach(function (a) {
if (!o[a.user]) {
o[a.user] = { title: a.title, user: a.user, events: [] };
r.push(o[a.user]);
}
o[a.user].events.push({ price: a.price, location: a.location });
});
return r;
}(arr);
document.write('<pre>' + JSON.stringify(grouped, 0, 4) + '</pre>');

Categories