Related
I have an array that looks something like this
const example = [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
];
As you can see, the organization name is something I want to key off of and create a data structure like this:
const output = [
// data.value will be their ID
{
organizationName: 'Organization A',
data: [
{ label: 'Person 1', value: '1' },
{ label: 'Person 2', value: '2' },
],
},
{
organizationName: 'Organization B',
data: [
{ label: 'Person 3', value: '3' },
],
},
]
What I've tried
I know I want to use reduce for something like this, but I feel like I'm off:
const providerOptions = externalPeople.data.reduce((acc, currentValue) => {
const {
organization: { name: organizationName },
} = currentValue;
if (organizationName) {
acc.push({ organization: organizationName, data: [] });
} else {
const { name: externalPersonName, id } = currentValue;
acc[acc.length - 1].data.push({ name: externalPersonName, value: id });
}
return acc;
}, [] as any);
However the output comes out to something like this:
[
{organizationName: 'Organization A', data: []},
{organizationName: 'Organization A', data: []},
{organizationName: 'Organization B', data: []},
];
data doesn't seem to get anything pushed inside the array in this reduce function, and the organization name get duplicated... what am I doing wrong?
Easiest way is to use an Map/Set/or object to keep track of orgs you create. This way you are not searching in the array to see if the organization was found already. After you are done, you can create the array you want from the object.
const externalPeople = {
data : [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
],
};
const providerOptions = Object.values(externalPeople.data.reduce((acc, currentValue) => {
const {
organization: { name: organizationName },
name: externalPersonName,
id
} = currentValue;
// Is the org new? Yes, create an entry for it
if (!acc[organizationName]) {
acc[organizationName] = { organization: organizationName, data: [] };
}
// push the person to the organization
acc[organizationName].data.push({ name: externalPersonName, value: id });
return acc;
}, {}));
console.log(providerOptions)
Here is another solution
const example = [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
];
const result = example.reduce((res, entry) => {
const recordIndex = res.findIndex(rec => rec.organizationName === entry.organization.name);
if(recordIndex >= 0) {
res[recordIndex].data.push({ label: entry.name, value: entry.id});
} else {
const record = {
organizationName: entry.organization.name,
data: [{ label: entry.name, value: entry.id }]
};
res.push(record);
}
return res;
}, []);
console.log(result);
You are not checking if the value is already present in your accumulation acc
You can check it with a simple find in the if statement since it's an array
const providerOptions = externalPeople.data.reduce((acc, currentValue) => {
const {
organization: { name: organizationName },
} = currentValue;
//Check if organization is not present already
if (!acc.find(a => a.organization === organizationName)) {
//Add also the data of the element your are processing
acc.push({ organization: organizationName, data: [{label: currentValue.name, value: currentValue.id}] });
} else {
const { name: externalPersonName, id } = currentValue;
acc[acc.length - 1].data.push({ label: externalPersonName, value: id });
}
return acc;
}, [] as any);
I also added the data of the first element of the group you create when adding the organization.
The result should be as your expected output:
[
{
organization: 'Organization A',
data: [
{ label: 'Person 1', value: '1' },
{ label: 'Person 2', value: '2' }
]
},
{
organization: 'Organization B',
data: [
{ label: 'Person 3', value: '3' }
]
}
]
Hope it helps!
Compare this solution (using Lodash) with other solutions. Which one emphasises your intentions at most? This is why we use Lodash in our company - to maintain code as declarative as we can, because code readability, with minimum cognitive overload, is most important goal during coding.
const persons = [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
];
const personsByOrganizations = _.groupBy(persons, 'organization.name')
const output = _.map(personsByOrganizations, (persons, organizationName) => ({
organizationName,
data: _.map(persons, ({ name, id }) => ({
label: name,
value: id
}))
}))
Something like that with using a Set?
result = [...new Set(example.map(d => d.organization.name))].map(label => {
return {
organizationName: label,
data: example.filter(d => d.organization.name === label).map(d => {
return {label: d.name, value: d.id}
})
}
})
`
What I've got is an array of objects with categories, dates and values, like so
const records =
[ { category: 'Cat 1', date: '2020-11-03', value: '300.00' }
, { category: 'Cat 2', date: '2020-11-03', value: '350.00' }
, { category: 'Cat 3', date: '2020-11-03', value: '50.00' }
, { category: 'Cat 1', date: '2020-11-13', value: '200.00' }
, { category: 'Cat 1', date: '2020-12-23', value: '100.00' }
, { category: 'Cat 2', date: '2020-11-23', value: '350.00' }
, { category: 'Cat 3', date: '2020-11-15', value: '50.00' }
, { category: 'Cat 3', date: '2021-01-15', value: '50.00' }
]
What I want to get is the new array with each category, month and monthly total value, like this:
const newRecords =
[ { category: 'Cat 1'
, totalPerMonth:
[ { month: '2020-11', totalMonthlyValue: '500.00' }
, { month: '2020-12', totalMonthlyValue: '100.00' }
] }
, { category: 'Cat 2'
, totalPerMonth:
[ { month: '2020-11', totalMonthlyValue: '700.00' }
] }
, { category: 'Cat 3'
, totalPerMonth:
[ { month: '2020-11', totalMonthlyValue: '100.00' }
, { month: '2021-01', totalMonthlyValue: '50.00' }
] } ]
this way
const records =
[ { category: 'Cat 1', date: '2020-11-03', value: '300.00' }
, { category: 'Cat 2', date: '2020-11-03', value: '350.00' }
, { category: 'Cat 3', date: '2020-11-03', value: '50.00' }
, { category: 'Cat 1', date: '2020-11-13', value: '200.00' }
, { category: 'Cat 1', date: '2020-12-23', value: '100.00' }
, { category: 'Cat 2', date: '2020-11-23', value: '350.00' }
, { category: 'Cat 3', date: '2020-11-15', value: '50.00' }
, { category: 'Cat 3', date: '2021-01-15', value: '50.00' }
]
const result =
Object.values(
records.reduce((r,{category,date,value}) =>
{
let Ym = date.substring(0,7)
if (!r[category]) r[category] = { category, sum : {} }
if (!r[category].sum[Ym]) r[category].sum[Ym] = 0
r[category].sum[Ym] += Number(value)
return r
},{}
)).map(({category,sum}) =>
{
let totalPerMonth = Object.entries(sum).map(([month,sum])=>({month,totalMonthlyValue:sum.toFixed(2)}))
return { category,totalPerMonth }
},{})
console.log ( result )
.as-console-wrapper {max-height: 100%!important;top:0 }
You can do this by grouping the data first by category and then by date. Then, you need to sum all values up and return the structure like defined. Here is an example which does exactly that.
const records = [
{category: 'Cat 1', date: '2020-11-03', value: '300.00'},
{category: 'Cat 2', date: '2020-11-03', value: '350.00'},
{category: 'Cat 3', date: '2020-11-03', value: '50.00'},
{category: 'Cat 1', date: '2020-11-13', value: '200.00'},
{category: 'Cat 1', date: '2020-12-23', value: '100.00'},
{category: 'Cat 2', date: '2020-11-23', value: '350.00'},
{category: 'Cat 3', date: '2020-11-15', value: '50.00'},
{category: 'Cat 3', date: '2021-01-15', value: '50.00'}
];
// first you need to group it by category, you can do that by
// assigning a group indicator as key to an object and add an
// array as value containing all values. you can also use lodash's
// groupBy for grouping values.
const groupedRecords = {};
records.forEach(record => {
if (groupedRecords[record.category] === undefined) {
groupedRecords[record.category] = [];
}
groupedRecords[record.category].push(record);
});
// then, you need to go through each category group and group all
// records again by date. you can use the same pattern as above.
const result = Object.entries(groupedRecords).map(([category, recordsOfOneCategory]) => {
const groupedDates = {};
recordsOfOneCategory.forEach(record => {
// this regex extracts the date per month. it's probably better
// to use a date library for this part
const month = record.date.match(/^(\d{4}-\d{2})/)[1];
if (groupedDates[month] === undefined) {
groupedDates[month] = [];
}
groupedDates[month].push(record);
});
// now you have for each category group a group of dates with
// records in it. you now need to make a sum for each group of
// dates. you can do this by mapping the values into an array of
// only values and then sum them up using reduce.
const totalPerMonth = Object.entries(groupedDates).map(([month, items]) => ({
month,
totalMonthlyValue: items.map(item => parseFloat(item.value)).reduce((a,b) => a + b).toFixed(2)
}));
return { category, totalPerMonth };
});
console.log(result);
You can group based on the category and month using array#reduce in an object. Then extract all the values from this object using Object.values() and array#map.
const records = [ { category: 'Cat 1', date: '2020-11-03', value: '300.00' } , { category: 'Cat 2', date: '2020-11-03', value: '350.00' } , { category: 'Cat 3', date: '2020-11-03', value: '50.00' } , { category: 'Cat 1', date: '2020-11-13', value: '200.00' } , { category: 'Cat 1', date: '2020-12-23', value: '100.00' } , { category: 'Cat 2', date: '2020-11-23', value: '350.00' } , { category: 'Cat 3', date: '2020-11-15', value: '50.00' } , { category: 'Cat 3', date: '2021-01-15', value: '50.00' } ],
result = records.reduce((r, {category, date, value}) => {
const month = date.substring(0, 7);
r[category] ??= {category};
r[category][month] ??= {month, total: 0};
r[category][month].total += +value;
return r;
},{}),
newRecords = Object
.values(result)
.map( ({category, ...rest}) =>
({ category,
totalPerMonth: Object.values(rest).map(({ month, total }) =>
({ month, totalMonthlyValue: total.toFixed(2) })
) }) );
console.log(newRecords);
.as-console-wrapper { max-height: 100% !important; top: 0; }
This question already has answers here:
How can I access and process nested objects, arrays, or JSON?
(31 answers)
How to loop through a plain JavaScript object with the objects as members
(28 answers)
Closed 1 year ago.
Given the following array of objects with dates in UTC:
const Arr = [
{
"name": "Person 1",
"date": "2021-02-28T14:00:00.000+0000"
},
{
"name": "Person 2",
"date": "2021-02-28T19:15:00.000+0000"
},
{
"name": "Person 3",
"date": "2021-04-04T18:30:00.000+0000"
},
{
"name": "Person 4",
"date": "2021-05-11T19:00:00.000+0000"
},
{
"name": "Person 5",
"date": "2021-05-12T18:45:00.000+0000"
},
{
"name": "Person 6",
"date": "2021-05-11T19:00:00.000+0000"
},
{
"name": "Person 7",
"date": "2021-05-23T15:00:00.000+0000"
}
];
I grouped the items by date using reduce as described in the below code:
const eventDate = {};
Arr.reduce((groupByDate, event) => {
const date = event.date.split('T')[0];
if (!groupByDate[date]) {
groupByDate[date] = [];
}
groupByDate[date].push(event);
return groupByDate;
}, {});
Getting an object grouped by date (as key) and an array of objects (as values):
{
'2021-02-28': [
{ name: 'Person 1', date: '2021-02-28T14:00:00.000+0000' },
{ name: 'Person 2', date: '2021-02-28T19:15:00.000+0000' }
],
'2021-04-04': [ { name: 'Person 3', date: '2021-04-04T18:30:00.000+0000' } ],
'2021-05-11': [
{ name: 'Person 4', date: '2021-05-11T19:00:00.000+0000' },
{ name: 'Person 6', date: '2021-05-11T19:00:00.000+0000' }
],
'2021-05-12': [ { name: 'Person 5', date: '2021-05-12T18:45:00.000+0000' } ],
'2021-05-23': [ { name: 'Person 7', date: '2021-05-23T15:00:00.000+0000' } ]
}
So my doubt here is how can I loop through that new object in order to get something like this?
(date in UTC will be formatted and get only the time)
2021-02-28:
name: Person 1 time: 14:00
name: Person 2 time: 19:15
2021-04-04:
name: Person 3 time: 18:30
2021-05-11:
name: Person 4 time: 19:00
name: Person 6 time: 19:00
2021-05-12:
name: Person 5 time: 18:45
2021-05-23:
name: Person 7 time: 15:00
Thanks in advance!
I would use Object.entries() or Object.keys() on the top level object to loop over it (for example using forEach() or any other looping method):
Object.entries(data).forEach(([key, items]) => {
console.log(key);
items.forEach(item => {
console.log("name:", item.name, "date:", item.date)
})
})
Playround
Perhaps something like this?
const obj = {
'2021-02-28': [
{ name: 'Person 1', date: '2021-02-28T14:00:00.000+0000' },
{ name: 'Person 2', date: '2021-02-28T19:15:00.000+0000' }
],
'2021-04-04': [{ name: 'Person 3', date: '2021-04-04T18:30:00.000+0000' }],
'2021-05-11': [
{ name: 'Person 4', date: '2021-05-11T19:00:00.000+0000' },
{ name: 'Person 6', date: '2021-05-11T19:00:00.000+0000' }
],
'2021-05-12': [{ name: 'Person 5', date: '2021-05-12T18:45:00.000+0000' }],
'2021-05-23': [{ name: 'Person 7', date: '2021-05-23T15:00:00.000+0000' }]
}
const newObj = {}
const newSubObj = {}
for (const [key, value] of Object.entries(obj)) {
newObj[key] = []
newSubObj[key] = []
for (const item of value) {
const date = new Date(item.date)
const subDate = item.date.substr(11, 5)
const hours = date.getUTCHours()
const minutes = date.getUTCMinutes()
newObj[key].push({ name: item.name, time: `${hours}:${minutes}` })
newSubObj[key].push({ name: item.name, time: subDate })
}
}
console.log(newObj, newSubObj)
Assuming you want to console.log() the result this is how I will do it:
const obj = {
'2021-02-28': [
{ name: 'Person 1', date: '2021-02-28T08:00:00.000+0000' },
{ name: 'Person 2', date: '2021-02-28T19:15:00.000+0000' }
],
'2021-04-04': [ { name: 'Person 3', date: '2021-04-04T18:30:00.000+0000' } ],
'2021-05-11': [
{ name: 'Person 4', date: '2021-05-11T19:00:00.000+0000' },
{ name: 'Person 6', date: '2021-05-11T19:00:00.000+0000' }
],
'2021-05-12': [ { name: 'Person 5', date: '2021-05-12T18:45:00.000+0000' } ],
'2021-05-23': [ { name: 'Person 7', date: '2021-05-23T15:00:00.000+0000' } ]
}
const printObj = (obj) => {
Object.keys(obj).forEach(key => {
console.log(`${key}:`);
obj[key].forEach(val => {
const currentDate = new Date(val.date);
const currentDateHours = (currentDate.getUTCHours() < 10 ? '0' : '') + currentDate.getUTCHours();
const currentDateMinutes = (currentDate.getUTCMinutes() < 10 ? '0' : '') + currentDate.getUTCMinutes();
console.log(`name: ${val.name} time: ${currentDateHours}:${currentDateMinutes}`);
})
});
}
printObj(obj);
Note: getUTCHours() and getUTCMinutes() will not show two digit number in case of a leading 0, I took it into consideration.
This question already has answers here:
How can I perform an inner join with two object arrays in JavaScript?
(7 answers)
Closed 1 year ago.
My database gives me the following data:
var responses = [
{ comment: 'Yes', uid: '5hg' },
{ comment: 'Maybe', uid: 'f1' },
{ comment: 'No', uid: 'b1k2' },
{ comment: 'Yes', uid: '6t2' },
{ comment: 'Yes', uid: 'hd1' },
];
var users = [
{ name: 'Trevor Hansen', group: 'Group 1', uid: 'f1' },
{ name: 'Britta Holt', group: 'Group 2', uid: '5hg' },
{ name: 'Jane Smith ', group: 'Group 2', uid: '6t2' },
{ name: 'Sandra Adams', group: 'Group 1', uid: 'c92c' },
{ name: 'Ali Connors', group: 'Group 1', uid: 'b2' },
{ name: 'John Smith', group: 'Group 2', uid: '9l2' },
{ name: 'Sandra Williams', group: 'Group 2', uid: 'hd1' },
{ name: 'Tucker Smith', group: 'Group 1', uid: 'b1k2' },
];
Because I store all of my user data only in users[] for different purposes I need to add some information to responses[] about the user (like their name and group). The uid is unique and can be used to match the data to a user.
Obviously there are less responses than users in responses[]. This should not affect my function and is an expected behavior.
This is the desired output:
var output = [
{ comment: 'Yes', uid: '5hg', name: 'Britta Holt', group: 'Group 2' },
{ comment: 'Maybe', uid: 'f1', name: 'Trevor Hansen', group: 'Group 1' },
{ comment: 'No', uid: 'b1k2', name: 'Tucker Smith', group: 'Group 1' },
{ comment: 'Yes', uid: '6t2', name: 'Jane Smith ', group: 'Group 2' },
{ comment: 'Yes', uid: 'hd1', name: 'Sandra Williams', group: 'Group 2' },
];
How can this be done? Any help is appreciated!
you can try for example:
const output = responses.map(response => {
const user = users.find(u => u.uid === response.uid);
return {...response, ...user}
})
or single liner:
const output = responses.map(response => ({...response, ...users.find(u => u.uid === response.uid)}));
I have two arrays of objects. One array contains list of items, another array contains list of categories. I want to create a new array based on categoryIds. I tried using lodash. But, couldn't get the correct solution.
I can do this using looping. But, I am looking for more clean approach.
var items = [
{
id: '001',
name: 'item1',
description: 'description of item1',
categoryId: 'cat1'
},
{
id: '002',
name: 'item2',
description: 'description of item2',
categoryId: 'cat2'
},
{
id: '003',
name: 'item3',
description: 'description of item3',
categoryId: 'cat1'
},
{
id: '004',
name: 'item4',
description: 'description of item4'
}
];
var categories = [
{
id: 'cat1',
name: 'Category1'
},
{
id: 'cat2',
name: 'Category2'
}
];
Expected output
[
{
categoryId: 'cat1',
name: 'Category1',
items: [
{
id: '001',
name: 'item1',
description: 'description of item1',
categoryId: 'cat1'
},
{
id: '003',
name: 'item3',
description: 'description of item3',
categoryId: 'cat1'
}
]
},
{
categoryId: 'cat2',
name: 'Category2',
items: [
{
id: '002',
name: 'item2',
description: 'description of item2',
categoryId: 'cat2'
}
]
},
{
categoryId: '',
name: '',
items: [
{
id: '004',
name: 'item4',
description: 'description of item4'
}
]
}
]
https://jsfiddle.net/sfpd3ppn/
Thanks for the help
The following does the trick:
var items = [{ id: '001', name: 'item1', description: 'description of item1', categoryId: 'cat1' }, { id: '002', name: 'item2', description: 'description of item2', categoryId: 'cat2' }, { id: '003', name: 'item3', description: 'description of item3', categoryId: 'cat1' }, { id: '004', name: 'item4', description: 'description of item4' } ];
var categories = [ { id: 'cat1', name: 'Category1' }, { id: 'cat2', name: 'Category2' } ];
var output = categories.concat([{id:'',name:''}]).map(function(v) {
return {
categoryId: v.id,
name: v.name,
items: items.filter(function(o) {
return o.categoryId === v.id || !o.categoryId && !v.id;
})
};
});
console.log(output);
I start by using .concat() to create a new categories array that holds the original categories items plus an "empty" category. Then I .map() that array to return category objects with your desired output structure, each of which has an items array that is produced by .filter()ing the original items array.
(Note that the items arrays within the output contain references to the same objects that were in the original items input, not copies of them. If you wanted copies you could add another .map() after the .filter().)
You can accomplish the desired result using a reduce. We are going to start with the original categories array and reduce the items array into it.
var items = [
{ id: '001', name: 'item1', description: 'description of item1', categoryId: 'cat1' },
{ id: '002', name: 'item2', description: 'description of item2', categoryId: 'cat2' },
{ id: '003', name: 'item3', description: 'description of item3', categoryId: 'cat1' },
{ id: '004', name: 'item4', description: 'description of item4' }
];
var categories = [
{ id: 'cat1', name: 'Category1' },
{ id: 'cat2', name: 'Category2' }
];
// Lets add the empty category at the beginning. This simplifies the logic.
categories.push({ id: '', name: '' });
// This is a function that will return a function to be used as a filter later on
function createFilter (category) {
return function (item) {
return item.id === category;
};
}
var mergedSet = items.reduce(function (previous, current) {
// Get the category ID of the current item, if it doesn't exist set to empty string
var categoryId = current.categoryId || '';
// Find the cateogry that matches the category ID
var category = previous.find(createFilter(categoryId));
// If the items property doesn't exists (we don't have any items), create an empty array
if (!category.items) { category.items = []; }
// Add the item the category
category.items.push(current);
// Return the current value that will be used in the next iteration.
// Note, the initial value of previous will be the intial value of categories.
return previous;
}, categories);
console.log(mergedSet);
/* Output
[
{ id: 'cat1',
name: 'Category1',
items:
[ { id: '001',
name: 'item1',
description: 'description of item1',
categoryId: 'cat1' },
{ id: '003',
name: 'item3',
description: 'description of item3',
categoryId: 'cat1' }
]
},
{ id: 'cat2',
name: 'Category2',
items:
[ { id: '002',
name: 'item2',
description: 'description of item2',
categoryId: 'cat2'
}
]
},
{ id: '',
name: '',
items:
[ { id: '004',
name: 'item4',
description: 'description of item4' } ] }
]
*/
Assuming the variables categories and items are assigned as you defined above:
const keyedCategories = _(categories)
.concat({ id: '', name: '' })
.keyBy('id')
.value();
const groupedItems = _.groupBy(items, (item) => _.get(item, 'categoryId', ''));
const result = _.reduce(groupedItems, (acc, value, key) => {
const { id: categoryId, name } = keyedCategories[key];
return _.concat(acc, { categoryId, name, items: value });
}, []);