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?
CodePudding user response:
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);
CodePudding user response:
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.