Group Array of Objects to Year and Month by Date - javascript

Given the following Array of Objects:
[{
"id": 1,
"name": "random_name1",
"published_at": "2021-01-16T08:52:24.408Z",
},
{
"id": 2,
"name": "random_name2",
"published_at": "2022-02-16T08:52:24.408Z",
},
{
"id": 3,
"name": "random_name3",
"published_at": "2020-04-16T08:52:24.408Z",
},
{
"id": 4,
"name": "random_name4",
"published_at": "2020-04-16T08:52:24.408Z",
},
{
"id": 5,
"name": "random_name5",
"published_at": "2022-05-16T08:52:24.408Z",
}
]
I need to group the items in one array of nested objects (descending) by Year and Month, result should be:
[
{
year: '2022',
months: [
{
month: '5',
items: [
{
id: '5',
name: 'random_name5'
}
]
},
{
month: '2',
items: [
{
id: '2',
name: 'random_name2'
}
]
}
]
},
{
year: '2021',
months: [
{
month: '1',
items: [
{
id: '1',
name: 'random_name1'
}
]
},
{
month: '2',
items: [
{
id: '2',
name: 'random_name2'
}
]
}
]
},
{
year: '2020',
months: [
{
month: '4',
items: [
{
id: '3',
name: 'random_name3'
},
{
id: '4',
name: 'random_name4'
}
]
}
]
}
];
I have tried the following:
items = [...new Set(items.map((item) => parseInt(item.published_at.split('-')[0])))].map((year) => [
{
year: year,
months: [
...new Set(
items
.filter((item) => parseInt(item.published_at.split('-')[0]) === year)
.map((item) => parseInt(item.published_at.split('-')[1]))
)
].map((month) => [
{
month: month,
items: items.filter(
(item) => parseInt(item.published_at.split('-')[0]) === year && parseInt(item.published_at.split('-')[1]) === month
)
}
])
}
]);
return items
The problem with the above solution, is that it will create a two dimensional array like so (months being two dimensional too):
[
[ { year: 2022, months: [Array] } ],
[ { year: 2021, months: [Array] } ],
[ { year: 2020, months: [Array] } ],
[ { year: 2019, months: [Array] } ],
[ { year: 2018, months: [Array] } ]
]
How to fix this?

If you get a unique list of year-months you can use this to map your object
const items = [{ "id": 1,"name": "random_name1","published_at": "2021-01-16T08:52:24.408Z", },
{ "id": 2, "name": "random_name2", "published_at": "2022-02-16T08:52:24.408Z",},
{ "id": 3, "name": "random_name3","published_at": "2020-04-16T08:52:24.408Z",},
{"id": 4, "name": "random_name4", "published_at": "2020-04-16T08:52:24.408Z",},
{ "id": 5, "name": "random_name5", "published_at": "2022-05-16T08:52:24.408Z",}]
let uniqueYearMonths = [... new Set(items.map(x => x.published_at.substring(0,7)))];
let results = [... new Set(items.map(x => x.published_at.substring(0,4)))]
.map(year => ({
year: year,
months: uniqueYearMonths
.filter(ym => ym.startsWith(year))
.map(ym => ({
month: ym.substring(5,7),
items: items
.filter(item => item.published_at.startsWith(ym))
.map(item => ({
id: item.id,
name: item.name
}))
}))
}));
console.log(results);

Given you array as data, you could do something with array methods like map and reduce.
Like this:
const groupedByYear = data.map((e) => ({ ...e, published_at: new Date(e.published_at) }))
.reduce((acc, e) => {
const year = e.published_at.getFullYear();
const month = e.published_at.getMonth() + 1;
if (!acc[year]) acc[year] = { year };
if (!acc[year][month]) acc[year][month] = [];
acc[year][month] = e;
return acc;
}, {})
const result = Object.values(groupedByYear).reduce((acc, e) => {
const { year, ...months } = e;
acc.push({ year: year, months: months });
return acc;
}, [])
This is an example and is probably not the best way to do this. It is only intended to show you a path of data transformations.
First data.map to be able to do operations on dates. Then a reduce to group data (here using an object). Then creating an array from the object values to match the output you want.
Compared to a solution like you showed, there is the advantage that you limit the number of times that you iterate over the array. It is always a good idea to avoid iterating to much time on an array for better performance.

Related

Merging nested array by object's ID [duplicate]

This question already has answers here:
How can I group an array of objects by key?
(32 answers)
Merge JavaScript objects in array with same key
(15 answers)
Closed 7 months ago.
I have an array of objects each with a nested array and a non-unique ID. I would like to concatenate the objects subarrays where the ID's match.
With the following input:
inputArr = [
{id: 1,
days: ["2022-09-05",
"2022-09-06",
]
},
{id: 2,
days: ["2022-10-05",
"2022-10-06"]
},
{id: 1,
days: ["2022-09-05",
"2022-09-06",
"2022-09-07",
"2022-09-08"]
},
{id: 2,
days: ["2022-10-05",
"2022-10-08"]
},
]
My desired output is as follows:
outputArr = [
{id: 1,
days: ["2022-09-05",
"2022-09-06",
"2022-09-07",
"2022-09-08"
]
},
{id: 2,
days: ["2022-10-05",
"2022-10-06",
"2022-10-08"]
},
]
Ideally I would like to do this without the use of for loops and instead use a map, filter, reduce strategy. I have tried a couple of variations but am having trouble with the nesting. Thanks in advance for the help! Its greatly appreciated.
Not an elegant solution but something like this could work:
let inputArr = [
{ id: 1, days: ["2022-09-05", "2022-09-06"] },
{ id: 2, days: ["2022-10-05", "2022-10-06"] },
{ id: 1, days: ["2022-09-05", "2022-09-06", "2022-09-07", "2022-09-08"] },
{ id: 2, days: ["2022-10-05", "2022-10-08"] },
];
let outputObj = {};
inputArr.forEach(
(item) =>
(outputObj[item.id] = outputObj[item.id]
? [...outputObj[item.id], ...item.days]
: item.days)
);
let outputArr = Object.entries(outputObj).map((item) => ({
id: parseInt(item[0]),
days: Array.from(new Set(item[1])),
}));
Output:
[
{
id: 1,
days: [ '2022-09-05', '2022-09-06', '2022-09-07', '2022-09-08' ]
},
{ id: 2, days: [ '2022-10-05', '2022-10-06', '2022-10-08' ] }
]
My approach using reduce
const inputArr = [
{ id: 1, days: ["2022-09-05", "2022-09-06"] },
{ id: 2, days: ["2022-10-05", "2022-10-06"] },
{ id: 1, days: ["2022-09-05", "2022-09-06", "2022-09-07", "2022-09-08"] },
{ id: 2, days: ["2022-10-05", "2022-10-08"] },
];
const outputArr = inputArr.reduce((accArr, currObj) => {
let ind = accArr.findIndex((obj) => obj.id == currObj.id);
if (ind < 0) {
accArr.push({ ...currObj, days: [...new Set(currObj.days)].slice().sort() });
} else {
accArr[ind].days = [...new Set([...accArr[ind].days, ...currObj.days])].slice().sort();
}
return accArr;
}, []);
console.log(JSON.stringify(outputArr, null, 4));
Result
[
{
"id": 1,
"days": [
"2022-09-05",
"2022-09-06",
"2022-09-07",
"2022-09-08"
]
},
{
"id": 2,
"days": [
"2022-10-05",
"2022-10-06",
"2022-10-08"
]
}
]
Note: [...new Set(array)] to get only unique values from that array.

Group array by year and month

I would like to group an array with events by year and month. My data looks like this:
const events = [
{
name: "event 1",
year: 2021,
month: 1,
},
{
name: "event 2",
year: 2021,
month: 9,
},
{
name: "event 3",
year: 2021,
month: 1,
},
{
name: "event 4",
year: 2022,
month: 7,
},
]
And my expected outcome should be something like this:
[
{
year: 2021,
month: 1,
events: [
{
name: "event 1"
},
{
name: "event 3"
}
]
},
{
year: 2021,
month: 9,
events: [
{
name: "event 2"
}
]
}
]
What would be the best approach to do this? I found a couple stackoverflow posts to group an array by it's key value but that not what I'm looking for.
const groupBy = (array, key) => {
return array.reduce((result, currentValue) => {
// If an array already present for key, push it to the array. Else create an array and push the object
(result[currentValue[key]] = result[currentValue[key]] || []).push(currentValue);
// Return the current iteration `result` value, this will be taken as next iteration `result` value and accumulate
return result;
}, {}); // empty object is the initial value for result object
};
const groupedByYear = groupBy(events, 'year');
You can do this with reduce and Object.values
const events = [
{
name: "event 1",
year: 2021,
month: 1,
},
{
name: "event 2",
year: 2021,
month: 9,
},
{
name: "event 3",
year: 2021,
month: 1,
},
];
const result = Object.values(events.reduce( (acc,evt) => {
const key = `${evt.year}-${evt.month}`;
if(!acc[key]) {
acc[key] = {year: evt.year, month: evt.month, events:[]}
}
acc[key].events.push( {name:evt.name} );
return acc;
},{}));
console.log(result);
You could take a dynamic approach by using a combined key for wanted properties for grouping.
Then remove all keys of grouing and push a new object without unwanted properties.
const
events = [{ name: "event 1", year: 2021, month: 1 }, { name: "event 2", year: 2021, month: 9 }, { name: "event 3", year: 2021, month: 1 }],
keys = ['year', 'month'],
result = Object.values(events.reduce((r, o) => {
let value,
key = keys.map(k => o[k]).join('|');
if (!r[key]) r[key] = { ...Object.fromEntries(keys.map(k => [k, o[k]])), events: [] };
r[key].events.push(keys.reduce((t, k) => (({ [k]: value, ...t } = t), t), o));
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to Group JavaScript Array of Object based on key

So I have a data like this
const carts = [
{
name: 'Voucher A',
participants: [
{
date: 112
},
{
date: 112
}
],
supplierName: 'ABC',
ticketDescription: 'Description of',
...data
},
{
name: 'Voucher B',
participants: [
{
date: 111
},
{
date: 112
}
],
supplierName: 'ABC',
ticketDescription: 'Description of',
...data
}
]
And I want to group it based on the date (if it has same date). So for data above, the expected result will be
expected = [
{
name: 'Voucher A',
date: 1,
count: 1,
supplierName: 'ABC',
ticketDescription: 'Description of',
...data
},
{
name: 'Voucher A',
date: 2,
count: 1,
supplierName: 'ABC',
ticketDescription: 'Description of',
...data
}
]
Because it has different date. But if it has same date, the expected result will be
expected = [
{
name: 'Voucher A',
date: 1,
count: 2,
supplierName: 'ABC',
ticketDescription: 'Description of',
...data
}
]
I was trying to use reduce to group it but it did not give the structure I want
carts.forEach(cart => {
cart.participants.reduce((acc, obj) => {
acc[obj.date] = [...acc[obj.date] || [], obj]
return acc
}, {})
})
To organize the data, I think you need two associations to group by: the name and the dates and their counts for that name:
const carts = [
{
name: 'Voucher A',
participants: [
{
date: 1
},
{
date: 2
}
]
}
];
const groupedByNames = {};
for (const { name, participants } of carts) {
if (!groupedByNames[name]) groupedByNames[name] = {};
for (const { date } of participants) {
groupedByNames[name][date] = (groupedByNames[name][date] || 0) + 1;
}
}
const output = Object.entries(groupedByNames).flatMap(
([name, dateCounts]) => Object.entries(dateCounts).map(
([date, count]) => ({ name, date: Number(date), count })
)
);
console.log(output);
If you want use, just plain for loops, you can try this solution. It looks simple and elegant 😜😜
const carts = [
{
name: 'Voucher A',
participants: [
{
date: 1
},
{
date: 1
},
{
date: 2
}
]
},
{
name: 'Voucher B',
participants: [
{
date: 1
},
{
date: 2
},
{
date: 2
}
]
}
]
const finalOutput = []
for (const cart of carts) {
for (const participant of cart.participants) {
const res = finalOutput.find(e => e.name === cart.name && e.date === participant.date)
if (res) {
res.count += 1
} else {
finalOutput.push({ name: cart.name, date: participant.date, count: 1 })
}
}
}
console.log(finalOutput)
Use forEach and destructuring
const process = ({ participants, name }) => {
const res = {};
participants.forEach(({ date }) => {
res[date] ??= { name, count: 0, date };
res[date].count += 1;
});
return Object.values(res);
};
const carts = [
{
name: "Voucher A",
participants: [
{
date: 1,
},
{
date: 2,
},
],
},
];
console.log(carts.flatMap(process));
const carts2 = [
{
name: "Voucher A",
participants: [
{
date: 1,
},
{
date: 1,
},
],
},
];
console.log(carts2.flatMap(process));

How to group from array object

I using code form "
I am looking for best ways of doing this. I have group:
data
[
{
"date": "16/04/2020",
"count": 0,
"name": "A"
},
{
"date": "16/04/2020",
"count": 1,
"name": "B"
},
{
"date": "17/04/2020",
"count": 0,
"name": "B"
}
//...More.....
]
Answer
{
"date": "04/2020",
"symtom": {
"data": [
{
"date": "16/04/2020",
"data": [
{
"name": "A",
"count": [
{
"date": "16/04/2020",
"count": 0,
"name": "A"
}
]
},
{
"name": "B",
"count": [
{
"date": "16/04/2020",
"count": 1,
"name": "B"
}
]
},
//...More.....
]
},
{
"date": "17/04/2020",
"data": [
{
"name": "B",
"count": [
{
"date": "17/04/2020",
"count": 0,
"name": "B"
}
]
},
//...More.....
]
}
]
}
}
Can I fix the code and to get the desired answer?
Code :
const items = [
{
tab: 'Results',
section: '2017',
title: 'Full year Results',
description: 'Something here',
},
{
tab: 'Results',
section: '2017',
title: 'Half year Results',
description: 'Something here',
},
{
tab: 'Reports',
section: 'Marketing',
title: 'First Report',
description: 'Something here',
}
];
function groupAndMap(items, itemKey, childKey, predic){
return _.map(_.groupBy(items,itemKey), (obj,key) => ({
[itemKey]: key,
[childKey]: (predic && predic(obj)) || obj
}));
}
var result = groupAndMap(items,"tab","sections",
arr => groupAndMap(arr,"section", "items"));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
ref : Group array of object nesting some of the keys with specific names
But I would like to have the answer line this (Answer) :
{
"date": "04/2020",
"symtom": {
"data": [
{
"date": "16/04/2020",
"data": [
{
"name": "A",
"count": 0,
},
{
"name": "B",
"count": 1,
},
//...More.....
]
},
{
"date": "17/04/2020",
"data": [
{
"name": "B",
"count":0,
},
//...More.....
]
}
]
}
}
Thanks!
I am a beginner but it looks like you want system.data.data to = an array of objects with the keys name:str and count:number but instead you are applying the whole object into count so the key count:{name:A, count:0,date:etc}.
I really can't follow your function which separates the data... but all you should have to do is when count is sent the object to reference just do a dot notation like object.count to access the number vs the object that way you will have the desired affect. Hopefully that is what you were asking.
I would use a helper function groupBy (this version is modeled after the API from Ramda [disclaimer: I'm one of its authors], but it's short enough to just include here.) This takes a function that maps an object by to a key value, and then groups your elements into an object with those keys pointing to arrays of your original element.
We need to use that twice, once to group by month and then inside the results to group by day. The rest of the transform function is just to format your output the way I think you want.
const groupBy = (fn) => (xs) =>
xs .reduce((a, x) => ({... a, [fn(x)]: [... (a [fn (x)] || []), x]}), {})
const transform = (data) =>
Object .entries (groupBy (({date}) => date.slice(3)) (data)) // group by month
.map (([date, data]) => ({
date,
symtom: {
data: Object .entries (groupBy (({date}) => date) (data)) // group by day
.map (([date, data]) => ({
date,
data: data .map (({date, ...rest}) => ({...rest})) // remove date property
}))
}
}))
const data = [{date: "16/04/2020", count: 0, name: "A"}, {date: "16/04/2020", count: 1, name: "B"}, {date: "17/04/2020", count: 0, name: "B"}, {date: "03/05/2020", count: 0, name: "C"}];
console .log (
transform (data)
)
.as-console-wrapper {min-height: 100% !important; top: 0}
If you need to run in an environment without Object.entries, it's easy enough to shim.
You could take a function for each nested group and reduce the array and the grouping levels.
var data = [{ date: "16/04/2020", count: 0, name: "A" }, { date: "16/04/2020", count: 1, name: "B" }, { date: "17/04/2020", count: 0, name: "B" }],
groups = [
(o, p) => {
var date = o.date.slice(3),
temp = p.find(q => q.date === date);
if (!temp) p.push(temp = { date, symptom: { data: [] } });
return temp.symptom.data;
},
({ date }, p) => {
var temp = p.find(q => q.date === date);
if (!temp) p.push(temp = { date, data: [] });
return temp.data;
},
({ date, ...o }, p) => p.push(o)
],
result = data.reduce((r, o) => {
groups.reduce((p, fn) => fn(o, p), r);
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

JS - merge object same array lodash

I need to transform a array of multiple object in one array object,
I explain myself, I wish to group for each table the objects that carry the same "month" and replace the properties that have the same value by adding their ID at the beginning:
ex:
quantity: 1
becomes fpsIssuedQuantity (in camelCase). http://jsfiddle.net/rLjQx/96589/
here are my data :
var data = {
"2018-01": [
{ id:"fpsIssued", month:"2018-01", quantity:"28" },
{ id:"dgfipIncome", month:"2018-01", amount:1350 },
{ id:"antaiPaidFps", month:"2018-01", quantity:2242 }
],
"2018-02": [
{ id: "fpsIssued", month: "2018-02", quantity: "29" },
{ id: "dgfipIncome", month: "2018-02", amount: 8530 },
{ id: "antaiPaidFps", month: "2018-02", quantity: 4857}
]
};
console.log(data);
and the expected data :
var expectedData = {
"2018-01": [
{ month: "2018-01", fpsIssuedquantity: "28",
dgfipIncomeamount: 1350, antaiPaidFpsQuantity: 2242
}
],
"2018-02": [
{ month: "2018-02", fpsIssuedquantity: "29",
dgfipIncomeamount: 8530, antaiPaidFpsQuantity: 4857
}
]
};
console.log(expectedData);
i use lodash and angularjs but i can not get my result .. please could you help me?
You could map new objects with wanted new property names and values in new objects.
var data = { "2018-01": [{ id: "fpsIssued", month: "2018-01", quantity: "28" }, { id: "dgfipIncome", month: "2018-01", amount: 1350 }, { id: "antaiPaidFps", month: "2018-01", quantity: 2242 }], "2018-02": [{ id: "fpsIssued", month: "2018-02", quantity: "29" }, { id: "dgfipIncome", month: "2018-02", amount: 8530 }, { id: "antaiPaidFps", month: "2018-02", quantity: 4857 }] },
result = Object.assign(
...Object
.entries(data)
.map(
([k, v]) => ({ [k]: Object.assign(...v.map(o => ({ month: o.month, [o.id + ('quantity' in o ? 'Quantity' : 'Amount')]: 'quantity' in o ? o.quantity : o.amount }))) })
)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources