I am working with the Toggl API and it is returning a JSON object containing data like so (edited to simplify)
{
"user_id": 12345,
"username": "JohnSmith",
"description": "foo",
"time_entries": [
{
"id": 3778920070,
"seconds": 10800,
"start": "2025-01-27T11:00:00+00:00",
"stop": "2025-01-27T14:00:00+00:00",
"at": "2025-01-27T16:17:24+00:00",
"at_tz": "2025-01-27T16:17:24+00:00"
}
],
"row_number": 1
},
{
"user_id": 6789,
"username": "JaneSmith",
"description": "bar",
"time_entries": [
{
"id": 3778684944,
"seconds": 5977,
"start": "2025-01-27T14:35:31+00:00",
"stop": "2025-01-27T16:15:08+00:00",
"at": "2025-01-27T16:15:09+00:00",
"at_tz": "2025-01-27T16:15:09+00:00"
}
],
"row_number": 2
},
{
"user_id": 12345,
"username": "JohnSmith",
"description": "bar",
"time_entries": [
{
"id": 3780521038,
"seconds": 3600,
"start": "2025-01-27T09:00:00+00:00",
"stop": "2025-01-27T10:00:00+00:00",
"at": "2025-01-28T13:34:31+00:00",
"at_tz": "2025-01-28T13:34:31+00:00"
}
],
"row_number": 3
},
I would like to read the array and copy a subsection of the data into a new array. Ideally the output would group matching usernames, for example
{
"username": "JohnSmith",
"description": "foo",
"time_entries": [
{
"id": 3778920070,
"seconds": 10800,
"start": "2025-01-27T11:00:00+00:00",
"stop": "2025-01-27T14:00:00+00:00",
"at": "2025-01-27T16:17:24+00:00",
"at_tz": "2025-01-27T16:17:24+00:00"
},
{
"id": 3780521038,
"seconds": 3600,
"start": "2025-01-27T09:00:00+00:00",
"stop": "2025-01-27T10:00:00+00:00",
"at": "2025-01-28T13:34:31+00:00",
"at_tz": "2025-01-28T13:34:31+00:00"
}
],
],
"row_number": 1
},
{
"user_id": 6789,
"username": "JaneSmith",
"description": "bar",
"time_entries": [
{
"id": 3778684944,
"seconds": 5977,
"start": "2025-01-27T14:35:31+00:00",
"stop": "2025-01-27T16:15:08+00:00",
"at": "2025-01-27T16:15:09+00:00",
"at_tz": "2025-01-27T16:15:09+00:00"
}
],
"row_number": 2
},
{
In pseudo-code, I am thinking I should do something like
foreach element in array if key = username , push to new array if already exist only copy the time_entries key/value under that username
Is there a more elegant approach ?
You will need to group the entries by their user_id
. After you have the grouped, grab the values and map them.
During mapping, you can reassign the time_entries
and give it a row_number
based on the index.
Note: The Map.groupBy
was added to JavaScript in 2024, and it available in Chrome/Edge 117+, Firefox 119+, and Safari 17.4+. If this is not working, I included a polyfill below.
// Polyfill for new Map.groupBy function.
if (Map.groupBy === undefined) {
Map.groupBy = function (data, keyFn) {
if (typeof keyFn !== 'function') {
throw new TypeError('keyFn must be a function');
}
return data.reduce((acc, item) => {
const key = keyFn(item);
return acc.set(key, acc.getOrDefault(key, []).concat(item));
}, new Map());
};
Map.prototype.getOrDefault = function (key, defaultValue) {
return this.has(key) ? this.get(key) : defaultValue;
};
}
const data = getData();
const grouped = [...Map.groupBy(data, user => user.user_id).values()]
.map(([head, ...tail], index) => ({
...head,
time_entries: [...head.time_entries, ...tail.flatMap(t => t.time_entries)],
row_number: index + 1 // Might not be needed, do you need to reassign?
}));
console.log(grouped);
function getData() {
return [{
"user_id": 12345,
"username": "JohnSmith",
"description": "foo",
"time_entries": [{
"id": 3778920070,
"seconds": 10800,
"start": "2025-01-27T11:00:00+00:00",
"stop": "2025-01-27T14:00:00+00:00",
"at": "2025-01-27T16:17:24+00:00",
"at_tz": "2025-01-27T16:17:24+00:00"
}],
"row_number": 1
}, {
"user_id": 6789,
"username": "JaneSmith",
"description": "bar",
"time_entries": [{
"id": 3778684944,
"seconds": 5977,
"start": "2025-01-27T14:35:31+00:00",
"stop": "2025-01-27T16:15:08+00:00",
"at": "2025-01-27T16:15:09+00:00",
"at_tz": "2025-01-27T16:15:09+00:00"
}],
"row_number": 2
}, {
"user_id": 12345,
"username": "JohnSmith",
"description": "bar",
"time_entries": [{
"id": 3780521038,
"seconds": 3600,
"start": "2025-01-27T09:00:00+00:00",
"stop": "2025-01-27T10:00:00+00:00",
"at": "2025-01-28T13:34:31+00:00",
"at_tz": "2025-01-28T13:34:31+00:00"
}],
"row_number": 3
}];
}
.as-console-wrapper { top: 0; max-height: 100% !important; }
After running the snippet above, you should get the following output:
[
{
"user_id": 12345,
"username": "JohnSmith",
"description": "foo",
"time_entries": [
{
"id": 3778920070,
"seconds": 10800,
"start": "2025-01-27T11:00:00+00:00",
"stop": "2025-01-27T14:00:00+00:00",
"at": "2025-01-27T16:17:24+00:00",
"at_tz": "2025-01-27T16:17:24+00:00"
},
{
"id": 3780521038,
"seconds": 3600,
"start": "2025-01-27T09:00:00+00:00",
"stop": "2025-01-27T10:00:00+00:00",
"at": "2025-01-28T13:34:31+00:00",
"at_tz": "2025-01-28T13:34:31+00:00"
}
],
"row_number": 1
},
{
"user_id": 6789,
"username": "JaneSmith",
"description": "bar",
"time_entries": [
{
"id": 3778684944,
"seconds": 5977,
"start": "2025-01-27T14:35:31+00:00",
"stop": "2025-01-27T16:15:08+00:00",
"at": "2025-01-27T16:15:09+00:00",
"at_tz": "2025-01-27T16:15:09+00:00"
}
],
"row_number": 2
}
]