Related
This is a bit tricky to find the right words, so hopefully showing some code will help.
I have the following (simplified) array of people and their departments. This comes from CMS data which allows a person to be added to multiple departments (hence why departments is an array).
[
{
id: '12345',
name: 'Person 1',
jobTitle: 'Engineering director',
departments: ['Engineering', 'Leadership']
},
{
id: '54321',
name: 'Person 2',
jobTitle: 'Junior engineer',
departments: ['Engineering']
},
{
id: '00001',
name: 'Person 3',
jobTitle: 'Founder',
departments: ['Leadership']
},
{
id: '00099',
name: 'Person 4',
jobTitle: 'No department',
departments: []
}
]
The result I'm after is to get the unique values of departments and create arrays for each, with the appropriate users inside it, so something like:
{
'Engineering': [
{
id: '12345',
name: 'Person 1',
jobTitle: 'Engineering director',
departments: ['Engineering', 'Leadership']
},
{
id: '54321',
name: 'Person 2',
jobTitle: 'Junior engineer',
departments: ['Engineering']
}
],
'Leadership': [
{
id: '12345',
name: 'Person 1',
jobTitle: 'Engineering director',
departments: ['Engineering', 'Leadership']
},
{
id: '00001',
name: 'Person 3',
jobTitle: 'Founder',
departments: ['Leadership']
}
]
}
I've got a groupBy function in my code already, but it doesn't quite do what I want (because it's not expecting an array as the property value), so if a person has multiple departments, I get an array with a concatenated name of both departments, but I want the same person to appear in multiple arrays instead.
This is my current groupBy function, but it's now distracting me and reduce is a concept my brain just really struggles with...!
function groupBy(objectArray, property) {
return objectArray.reduce(function (acc, obj) {
var key = obj[property];
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj);
return acc;
}, {});
}
// which I can use like:
const groupedPeople = Object.entries(groupBy(people, "departments"));
// and it'll return
/*
{
'Engineering,Leadership': [
{
id: '12345',
name: 'Person 1',
jobTitle: 'Engineering director',
departments: ['Engineering', 'Leadership']
}
],
'Engineering': [
{
id: '54321',
name: 'Person 2',
jobTitle: 'Junior engineer',
departments: ['Engineering']
}
],
'Leadership': [
{
id: '00001',
name: 'Person 3',
jobTitle: 'Founder',
departments: ['Leadership']
}
]
}
*/
I feel like I'm close, but can't get my brain to engage!
const people = [{
id: '12345',
name: 'Person 1',
jobTitle: 'Engineering director',
departments: ['Engineering', 'Leadership']
},
{
id: '54321',
name: 'Person 2',
jobTitle: 'Junior engineer',
departments: ['Engineering']
},
{
id: '00001',
name: 'Person 3',
jobTitle: 'Founder',
departments: ['Leadership']
},
{
id: '00099',
name: 'Person 4',
jobTitle: 'No department',
departments: []
}
]
function groupBy(objectArray, property) {
return objectArray.reduce(function(acc, obj) {
var key = obj[property];
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj);
return acc;
}, {});
}
// which I can use like:
const groupedPeople = Object.entries(groupBy(people, "departments"));
console.log("Grouped:", groupedPeople);
var key = obj[property];
on this line in your code, the key variable represents the array of deparments, which you then use as acc[key]. What JS does it that in converts the array into string to be used as a key of the acc object and the process for that is to just join the array by commas. What you need is to loop over the array instead:
function groupBy(objectArray, property) {
return objectArray.reduce(function (acc, obj) {
var keys = obj[property];
keys.forEach(key => {
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj);
})
return acc;
}, {});
}
Such change will make it work for your use case, the groupBy function will no longer work if the key is not an array, so use with caution or make it support both strings and arrays.
I was getting a recursion error in my solution and had to go with making a deep copy of an object (JSON.parse(JSON.stringify(obj))) before pushing it into 2 different arrays. Also, considering your property might or might not be an array, I normalized that part with:
let keys = Array.isArray(obj[property]) ? obj[property] : [obj[property]];
let people = [{
id: '12345',
name: 'Person 1',
jobTitle: 'Engineering director',
departments: ['Engineering', 'Leadership']
},
{
id: '54321',
name: 'Person 2',
jobTitle: 'Junior engineer',
departments: ['Engineering']
},
{
id: '00001',
name: 'Person 3',
jobTitle: 'Founder',
departments: ['Leadership']
},
{
id: '00099',
name: 'Person 4',
jobTitle: 'No department',
departments: []
}
];
function groupBy(objectArray, property) {
return objectArray.reduce((acc, obj) => {
let keys = Array.isArray(obj[property]) ? obj[property] : [obj[property]];
keys.forEach(k => {
acc[k] = acc[k] || [];
acc[k].push(JSON.parse(JSON.stringify(obj)))
})
return acc;
}, {});
}
console.log(groupBy(people, "departments"));
console.log(groupBy(people, "jobTitle"));
I have only changed 3 lines of your snippet, converting everything to an array first, then looping through that array and creating groups makes the code easier. You could also check if it's an array and write similar code with an if statement.
const people = [{
id: '12345',
name: 'Person 1',
jobTitle: 'Engineering director',
departments: ['Engineering', 'Leadership']
},
{
id: '54321',
name: 'Person 2',
jobTitle: 'Junior engineer',
departments: ['Engineering']
},
{
id: '00001',
name: 'Person 3',
jobTitle: 'Founder',
departments: ['Leadership']
},
{
id: '00099',
name: 'Person 4',
jobTitle: 'No department',
departments: []
}
]
function groupBy(objectArray, property) {
return objectArray.reduce(function(acc, obj) {
if (obj[property] === null || obj[property] === undefined) return acc; // if obj[property] is not defined or it's null don't add it to groupts
if (obj[property].constructor !== Array) obj[property] = [obj[property]] // obj[property] is always an array now
obj[property].forEach(key => {
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj);
})
return acc;
}, {});
}
// which I can use like:
const groupedPeople = Object.entries(groupBy(people, "departments"));
console.log("Grouped:", groupedPeople);
I have this array of tasks:
[
{ id: 54321, Task: 'Task 1', Topics: ["111", "222"]},
{ id: 667566, Task: 'Task 2', Topics: ["222"] },
{ id: 76889, Task: 'Task 3', Topics: ["333"] },
]
and I want to restructure it based on duplicate strings inside Topics,
So the result should be:
[{
name: "111",
id: [54321]
}, {
name: "222",
id: [54321, 667566]
}, {
name: "333",
id: [76889]
}]
basically topic names should become unique and tasks id should group under the topic's name
You can use Array.reduce() to create the desired structure, we create a map of topics, using the topic name as the key, then use Object.values() to get the result array:
const tasks = [
{ id: 54321, Task: 'Task 1', Topics: ["111", "222"]},
{ id: 667566, Task: 'Task 2', Topics: ["222"] },
{ id: 76889, Task: 'Task 3', Topics: ["333"] },
]
const result = Object.values(tasks.reduce((acc, cur) => {
for(let topic of cur.Topics) {
if (!acc[topic]) acc[topic] = { name: topic, id: [] }
acc[topic].id.push(cur.id);
}
return acc;
}, {}))
console.log('Result:', result);
I have this array of objects, that initially only had name and unit fields. I recently added a unitConv field of array type.
I used to output an array of strings with the name and unit from every object.
const ingredients = [
{name: 'wine', unit: 'ml', unitConv: []},
{name: 'salt', unit: 'gr', unitConv: [{unitMeasure: { name: 'spoon'}}, {unitMeasure: { name: 'tea-spoon'}}]},
{name: 'onion', unit: 'piece', unitConv: []},
]
const response = ingredients.map(ing => `${ing.name} [${ing.unit}]`)
And this is the response:
["wine [ml]", "salt [gr]", "onion [piece]"]
Now that I added unitConv, I want to see if any unitConv are available in the object, and pass those too, as options, like this:
["wine [ml]", "salt [gr]", "onion [piece]", "salt[spoon]", "salt[tea-spoon]"]
And I want to keep the initial value of salt too, the one the uses the 'gr' as a unit. So for salt, because I have one unit and two unitConv, I want to output it three times, with each of this options.
If one of the objects doesn't have unitConv, the unitConv fields will appear as an empty array, like in the example above.
You can use Array#flatMap to create the second array to concatenate with the first.
const ingredients = [
{name: 'wine', unit: 'ml', unitConv: []},
{name: 'salt', unit: 'gr', unitConv: [{unitMeasure: { name: 'spoon'}}, {unitMeasure: { name: 'tea-spoon'}}]},
{name: 'onion', unit: 'piece', unitConv: []},
]
const response = ingredients.map(ing => `${ing.name} [${ing.unit}]`)
.concat(ingredients.flatMap(({name, unitConv})=>
unitConv.map(x => `${name} [${x.unitMeasure.name}]`)));
console.log(response);
a simple array reduce method:
const ingredients =
[ { name: 'wine', unit: 'ml', unitConv: [] }
, { name: 'salt', unit: 'gr', unitConv:
[ { unitMeasure: { name: 'spoon' } }
, { unitMeasure: { name: 'tea-spoon' } }
]
}
, { name: 'onion', unit: 'piece', unitConv: [] }
]
const resp2 = ingredients.reduce((resp,{name,unit,unitConv}) =>
{
resp.push( `${name} [${unit}]` )
unitConv.forEach(({unitMeasure}) =>
resp.push(`${name} [${unitMeasure.name}]`))
return resp
},[])
console.log( resp2 )
.as-console-wrapper{max-height:100% !important;top: 0;}
you can use flatMap and inside its callback function check for unitConv array lentgh, if it's true then you can use a map for that. here is the demo:
const ingredients = [{
name: 'wine',
unit: 'ml',
unitConv: []
},
{
name: 'salt',
unit: 'gr',
unitConv: [{
unitMeasure: {
name: 'spoon'
}
}, {
unitMeasure: {
name: 'tea-spoon'
}
}]
},
{
name: 'onion',
unit: 'piece',
unitConv: []
},
]
function strFormat(name, unit) {
return `${name} [${unit}]`;
}
const result = ingredients.flatMap((ing) => {
if (ing.unitConv.length) {
const ingAllUnits = ing.unitConv.map((unit) => strFormat(ing.name, unit.unitMeasure.name));
ingAllUnits.push(strFormat(ing.name, ing.unit));
return ingAllUnits;
} else return strFormat(ing.name, ing.unit);
});
console.log(result);
I have this data structure that i want to map in an es6 one-liner fashion:
const vehicles = [
{
id: 'vehicle1',
items: [
{
id: 'contract1'
name: 'Contract 1',
},
],
},
{
id: 'vehicle1',
items: [
{
id: 'contract2'
name: 'Contract 2',
},
],
},
{
id: 'vehicle2',
items: [
{
id: 'contract3'
name: 'Contract 3',
},
],
},
{
id: 'vehicle2',
items: [
{
id: 'contract4'
name: 'Contract 4',
},
],
},
]
I would like to collect this in a list like this:
const result = [
{
id: 'vehicle1',
items: [
{
id: 'contract1'
name: 'Contract 1',
},
{
id: 'contract2'
name: 'Contract 2',
},
],
},
{
id: 'vehicle2',
items: [
{
id: 'contract3'
name: 'Contract 3',
},
{
id: 'contract4'
name: 'Contract 4',
},
],
},
]
So the vehicles in list is unique and items is in one list.
I tried this but it only collects vehicles in list:
const res = vehicles.reduce((acc, vehicle) => acc.set(vehicle.id, vehicle), new Map())
How can I do this the 'ES6 way'?
Map would be not a good choice for this type of result, Map used mostly when you have to modify and get same structure. You can use reduce for this.
var data = [{
id: 'vehicle1',
items: [{
id: 'contract1',
name: 'Contract 1'
}]
},
{
id: 'vehicle1',
items: [{
id: 'contract2',
name: 'Contract 2'
}]
},
{
id: 'vehicle2',
items: [{
id: 'contract3',
name: 'Contract 3'
}]
},
{
id: 'vehicle2',
items: [{
id: 'contract4',
name: 'Contract 4'
}]
}
];
var result = {};
data.forEach(val => {
if (result[val.id])
result[val.id].items = result[val.id].items.concat(val.items);
else
result[val.id] = val
});
result = Object.values(result);
console.log(result);
You were on the right path. Here it is:
const res = vehicles.reduce((m,v)=>m.set(v.id, [...v.items, ...(m.get(v.id)||[])]), new Map)
This use array destructuring to concat items.
You can use Array.prototype.reduce to aggregate the input by id and Object.keys to get the output in the desired format
const vehicles=[{id:'vehicle1',items:[{id:'contract1',name:'Contract 1'}]},{id:'vehicle1',items:[{id:'contract2',name:'Contract 2'}]},{id:'vehicle2',items:[{id:'contract3',name:'Contract 3'}]},{id:'vehicle2',items:[{id:'contract4',name:'Contract 4'}]}];
const grouped = vehicles.reduce((all, {id, items}) => {
if (!all.hasOwnProperty(id)) all[id] = { id, items: [] };
all[id].items.push(...items);
return all;
}, {});
const result = Object.keys(grouped).map(k => grouped[k]);
console.log(result);
Not a one-liner but it returns desired result and uses ES6 Map.
const data = [{"id":"vehicle1","items":[{"id":"contract1","name":"Contract 1"}]},{"id":"vehicle1","items":[{"id":"contract2","name":"Contract 2"}]},{"id":"vehicle2","items":[{"id":"contract3","name":"Contract 3"}]},{"id":"vehicle2","items":[{"id":"contract4","name":"Contract 4"}]}]
const res = data.reduce((acc, {id, items}) => {
if(!acc.get(id)) acc.set(id, {id, items});
else acc.get(id).items.push(...items);
return acc;
}, new Map())
console.log([...res.values()])
Well, its not a one liner but it can be...if you delete all the line breaks :D
const convert = () => {
const vMap = vehicles.reduce((acc, vehicle) => {
if (acc[vehicle.id]) {
acc[vehicle.id].items.push(...vehicle.items);
} else {
acc[vehicle.id] = vehicle;
}
return acc;
}, {});
return Object.keys(vMap).map(k => vMap[k]);
};
convert();
Nearly, you could get the grouped items in a map and map the map with the wanted id and itmes property.
const
vehicles = [{ id: 'vehicle1', items: [{ id: 'contract1', name: 'Contract 1', }] }, { id: 'vehicle1', items: [{ id: 'contract2', name: 'Contract 2', }] }, { id: 'vehicle2', items: [{ id: 'contract3', name: 'Contract 3', }] }, { id: 'vehicle2', items: [{ id: 'contract4', name: 'Contract 4' }] }],
result = Array.from(
vehicles.reduce((acc, { id, items }) =>
acc.set(id, (acc.get(id) || []).concat(items)), new Map()),
([id, items]) => ({ id, items })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have an array of objects, that looks like this:
data = [
{
title: 'John Doe',
departments: [
{ name: 'Marketing', slug: 'marketing'},
{ name: 'Sales', slug: 'sales'},
{ name: 'Administration', slug: 'administration'},
]
},
{
title: 'John Doe Junior',
departments: [
{ name: 'Operations', slug: 'operations'},
{ name: 'Sales', slug: 'sales'},
]
},
{
title: 'Rick Stone',
departments: [
{ name: 'Operations', slug: 'operations'},
{ name: 'Marketing', slug: 'marketin'},
]
},
]
How can I iterate over each object's departments array and create new arrays where I would have employees sorted by departments, so that the end result would like this:
operations = [
{
title: 'John Doe Junior',
departments: [
{ name: 'Operations', slug: 'operations'},
{ name: 'Sales', slug: 'sales'},
]
},
{
title: 'Rick Stone',
departments: [
{ name: 'Operations', slug: 'operations'},
{ name: 'Marketing', slug: 'marketin'},
]
},
]
marketing = [
{
title: 'John Doe',
departments: [
{ name: 'Marketing', slug: 'marketing'},
{ name: 'Sales', slug: 'sales'},
{ name: 'Administration', slug: 'administration'},
]
},
{
title: 'Rick Stone',
departments: [
{ name: 'Operations', slug: 'operations'},
{ name: 'Marketing', slug: 'marketin'},
]
},
]
What would be the way to create dynamically this kind of arrays?
Update
I have tried to come up with a solution using the suggestion from the answer, where I would dynamically create an array with department objects that would have an array of employees:
const isInDepartment = departmentToCheck => employer => employer.departments.find(department => department.slug == departmentToCheck);
var departments = [];
function check(departments, name) {
return departments.some(object => name === object.department);
}
employees.forEach((employee) => {
employee.departments.forEach((department) => {
let found = check(departments, department.slug);
if (!found) {
departments.push({ department: department.slug });
}
});
});
departments.forEach((department) => {
// push an array of employees to each department
//employees.filter(isInDepartment(department));
});
But, I don't know how can I push the array of employees to the object in the array that I am looping at the end?
This is the fiddle.
How about this? I use Array.protoype.filter operation, and I use a higher-order function (in this case a function that returns a function) to create the predicate (function that returns a boolean) that will check whether an employee is in a specific department. I added some (hopefully) clarifying comments in the code.
Edit: with the new code and context you provided this JSFiddle demo shows how it would work together.
const employees = [
{
title: 'John Doe',
departments: [
{ name: 'Marketing', slug: 'marketing'},
{ name: 'Sales', slug: 'sales'},
{ name: 'Administration', slug: 'administration'}
]
},
{
title: 'John Doe Junior',
departments: [
{ name: 'Operations', slug: 'operations'},
{ name: 'Sales', slug: 'sales'}
]
},
{
title: 'Rick Stone',
departments: [
{ name: 'Operations', slug: 'operations'},
{ name: 'Marketing', slug: 'marketin'}
]
}
];
// given a department, this returns a function that checks
// whether an employee is in the specified department
// NOTE: the "find" returns the found object (truthy)
// or undefined (falsy) if no match was found.
const isInDepartment =
departmentToCheck =>
employee => employee.departments.find(dep => dep.name == departmentToCheck);
const employeesInMarketing = employees.filter(isInDepartment('Marketing'));
const employeesInOperations = employees.filter(isInDepartment('Operations'));
console.log('Employees in marketing', employeesInMarketing);
console.log('Employees in operations', employeesInOperations);