I have this code structure:
const data = [
{
name: 'Lebron',
sports: 'Basketball',
},
{
name: 'Durant',
sports: 'Basketball',
},
{
name: 'Federrer',
sports: 'Tennis',
},
{
name: 'Nadal',
sports: 'Tennis',
},
];
and I'm trying to transform it into this:
const treeData = [
{
title: 'Select All',
key: 'All',
children: [
{
title: 'Basketball',
key: 'Basketball',
children: [
{
title: 'Lebron',
key: 'Lebron',
},
{
title: 'Durant',
key: 'Durant',
},
],
},
{
title: 'Tennis',
key: 'Tennis',
children: [
{
title: 'Federrer',
key: 'Federrer',
},
{
title: 'Nadal',
key: 'Nadal',
},
],
},
],
},
];
to use in Ant Design Tree link.
Right now my plan is to get all the sports like this:
let sports = data.map(({ sports }) => sports);
sports = [...new Set(sports)];
but after that I have no idea on what should be my next step to achieve the treeData
You can use .reduce() to accumulate all sports to a Map, where each sport is a key and each value for the sport is an array of associated names. Once you have built the Map, you can use Array.from() to convert your map into your desired children array. This can be done by providing a mapping function as the second argument to Array.from(), and using it to convert each entry ([key, value]) pair to an object of your desired structure.
See example below:
const data = [ { name: 'Lebron', sports: 'Basketball', }, { name: 'Durant', sports: 'Basketball', }, { name: 'Federrer', sports: 'Tennis', }, { name: 'Nadal', sports: 'Tennis', }, ];
const children = Array.from(data.reduce((m, {name, sports}) =>
m.set(sports, [...(m.get(sports) || []), name])
, new Map), ([key, arr]) => ({title: key, key, children: arr.map(title => ({title, key: title}))}));
const treeData = [{title: 'Select All', key: 'All', children}];
console.log(treeData);
const data = [
{
name: 'Lebron',
sports: 'Basketball',
},
{
name: 'Durant',
sports: 'Basketball',
},
{
name: 'Federrer',
sports: 'Tennis',
},
{
name: 'Nadal',
sports: 'Tennis',
},
];
const dataMap: Record<string, string[]> = {};
data.forEach(({ name, sports }) => {
dataMap[sports] ??= []
dataMap[sports].push(name)
})
const treeData = [{
title: 'Select All',
key: 'All',
children: Object.entries(dataMap).map(([sport, names]) => ({
title: sport,
key: sport,
children: names.map(name => ({
title: name,
key: name
}))
}))
}]
Here's a two-steps way of get the children of your final object:
First, use a reduce to create an object of sports/children, where children matches what you have in your final output:
const middle = data.reduce((transformed, item) => {
if (!transformed[item.sports]) {
transformed[item.sports] = [];
}
transformed[item.sports].push({
title: item.name,
key: item.name
});
return transformed;
}, {});
Then map the results of the reduce function to reshape the object:
const results = Object.entries(middle).map(([key, children]) => ({
title: key,
key,
children
}));
Related
var employees = [
{ name: "Josh", title: "receptionist" },
{ name: "Naila", title: "receptionist" },
{ name: "Tom", title: "doctor" },
{ name: "Becky", title: "doctor" }
];
For example on this one I would like to return
{
'doctor':2,
'receptionist':2
}
This is what I have tried:
const convert = (employees) => {
const res = {};
employees.forEach((employee) => {
const key = `${employee.title}${employee["doctor-receptionist"]}`;
if (!res[key]) {
res[key] = {...employee, count: 0 };
};
res[key].count += 1;
});
return Object.values(res);
};
console.log(convert(employees));
It returns the name of the employees, which I did not want.
I also thought about creating arrays for each kind of job title and filtering each employee from the employee array, and pushing them to their respective arrays. But I feel like there must be an easier way.
Array#reduce is the way to go:
const employees = [ { name: "Josh", title: "receptionist" }, { name: "Naila", title: "receptionist" }, { name: "Tom", title: "doctor" }, { name: "Becky", title: "doctor" } ],
summary = employees
.reduce((acc,{title}) => ({...acc,[title]:(acc[title] || 0) + 1}),{});
console.log( summary );
Just using reduce() can do it
var employees = [
{ name: "Josh", title: "receptionist" },
{ name: "Naila", title: "receptionist" },
{ name: "Tom", title: "doctor" },
{ name: "Becky", title: "doctor" }
];
let result = employees.reduce((a,c) =>{
a[c.title] = a[c.title] ? a[c.title] + 1 : 1
return a
},{})
console.log(result)
The reduce iterator was built for this kind of thing.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
I also employ Object.values() since the way I am using reduce is to create an object to easily keep track of the data along the way. The object.values helps distill that into an array when done.
const employees = [
{ name: "Josh", title: "receptionist" },
{ name: "Naila", title: "receptionist" },
{ name: "Tom", title: "doctor" },
{ name: "Becky", title: "doctor" }];
const reduced = Object.values(employees.reduce((b,a) => {
if (!b[a.title]) b[a.title] = {title: a.title, count: 1}
else b[a.title].count++;
return b
},{}))
console.log(reduced);
you can try this on your code
const employees = [
{ name: "Josh", title: "receptionist" },
{ name: "Naila", title: "receptionist" },
{ name: "Tom", title: "doctor" },
{ name: "Becky", title: "doctor" }
]
const sumReceptionist = employees.filter((item)=>{
return item.title === 'receptionist'
}).length
const sumDoctor = employees.filter((item)=>{
return item.title === 'doctor'
}).length
let total =
{
receptionist: sumReceptionist,
doctor: sumDoctor
}
console.log(total)
I think this is what you're trying to do. You want the total of the positions from the employee list?
const Employees = [{
name: "Josh",
title: "receptionist"
},
{
name: "Naila",
title: "receptionist"
},
{
name: "Tom",
title: "doctor"
},
{
name: "Becky",
title: "doctor"
},
{
name: "Chad",
title: "doctor"
},
{
name: "Cindy",
title: "nurse"
}
];
// A forEach won't return an object or array, so we create one to modify within it
const PositionTotals = {};
Employees.forEach(employee => {
// Check if property exists. If not, create it and add one to it before continuing loop
if (!PositionTotals.hasOwnProperty(employee.title))
return PositionTotals[employee.title] = 1;
PositionTotals[employee.title]++;
})
console.log(PositionTotals);
$('#PositionTotals').html(JSON.stringify(PositionTotals, null, '\t'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.2.3/jquery.min.js"></script>
<pre id="PositionTotals"></pre>
How to loop through two arrays of objects and get a new array with some data modified?
Arrays:
const products = [
{
brand: 'Levis',
category: 'Jeans',
},
{
brand: 'Levis',
category: 'Jeans',
},
{
brand: 'Levis',
category: 'Tees',
},
];
const categories = [
{
name: 'Jeans',
},
{
name: 'Tees',
},
];
Need new categories array like this with new prop productCount:
const newCategories = [
{
name: 'Jeans',
productCount: 2,
},
{
name: 'Tees',
productCount: 0,
},
];
I tried this way but it doesn't work:
const newArr = categories.map((category) => {
let count = 0;
const index = products.findIndex((product) => category.name === product.category);
if (index > -1) {
return {
...category,
productCount: count++,
};
}
return {
...category,
productCount: 0,
};
});
Increasing the count number will not in that case because it will always start with zero. Instead, you can use the filter() method to find the number of products with a specific category and assign this number to productCount attribute.
const products = [{
brand: 'Levis',
category: 'Jeans',
},
{
brand: 'Levis',
category: 'Jeans',
},
{
brand: 'Levis',
category: 'Tees',
},
];
const categories = [{
name: 'Jeans',
},
{
name: 'Tees',
},
];
const newArr = categories.map((category) => {
const numberOfItems = products.filter((product) => category.name === product.category);
return {
...category,
productCount: numberOfItems.length,
};
});
console.log(newArr)
You can create an object and the transform it to array, something like this:
const products = [
{
brand: "Levis",
category: "Jeans"
},
{
brand: "Levis",
category: "Jeans"
},
{
brand: "Levis",
category: "Tees"
}
];
const categoriesObj = {};
products.forEach(({ brand, category }) => {
categoriesObj[category] ??= {
name: category,
productCount: 0
};
++categoriesObj[category].productCount;
});
const newCategories = Object.values(categoriesObj);
console.log(newCategories);
You can use the Array#Map method and add a productCount property using the Array#filter method
const products = [{
brand: 'Levis',
category: 'Jeans',
},
{
brand: 'Levis',
category: 'Jeans',
},
{
brand: 'Levis',
category: 'Tees',
},
];
const categories = [{
name: 'Jeans',
},
{
name: 'Tees',
},
];
const newCategories = [...categories].map(category => ({
...category,
productCount: products.filter(product => product.category === category.name).length
}))
console.log(newCategories)
You could do this with Array.reduce(), incrementing the productCount for each item. This should also be efficient, requiring only one iteration of the products array.
We'd run the reduce over both arrays, ensuring that we'll end up with a productCount of zero where no products for that category exist.
const products = [ { brand: 'Levis', category: 'Jeans', }, { brand: 'Levis', category: 'Jeans', }, { brand: 'Levis', category: 'Tees', }, ];
const categories = [ { name: 'Jeans', }, { name: 'Tees', }, { name: 'Foo', } ];
const result = Object.values([...categories, ...products].reduce((acc, { brand, category, name }) => {
const key = name || category;
acc[key] = acc[key] || { name: key, productCount: 0 };
if (category) acc[key].productCount++;
return acc;
}, {}));
console.log('Result:', result);
.as-console-wrapper { max-height: 100% !important; }
i have two arrays.
const department = [
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' },
];
const models = [
{
id: '23',
name: 'model1',
departments: [{ id: '1', name: 'department1' }],
},
{
id: '54',
name: 'model2',
departments: [
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' },
],
},
];
i need to render accordions with department names and accordion details with matching models names. My question is how to filter those arrays to get models
We can map through the departments array, and add a models property that equals the models array, but filtered only to the ones that contain a matching department id.
const departments = [
{ id: "1", name: "department1" },
{ id: "2", name: "department2" },
];
const models = [
{
id: "23",
name: "model1",
departments: [{ id: "1", name: "department1" }],
},
{
id: "54",
name: "model2",
departments: [
{ id: "1", name: "department1" },
{ id: "2", name: "department2" },
],
},
];
const getDepartmentsWithModels = () => {
return departments.map((department) => {
return {
...department,
models: models.filter((model) => {
const modelDepartmentIds = model.departments.map(({ id }) => id);
return modelDepartmentIds.includes(department.id);
}),
};
});
};
console.log(getDepartmentsWithModels());
// [ { id: '1', name: 'department1', models: [ [Object], [Object] ] },
// { id: '2', name: 'department2', models: [ [Object] ] } ]```
I've built some code, which iterates over the departments. For each department it iterates the models and for each model it checks if the department is within the model departments.
const department =
[
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' }
]
const models =
[
{
id: '23',
name: 'model1',
departments: [{ id: '1', name: 'department1' }]
},
{
id: '54',
name: 'model2',
departments: [{ id: '1', name: 'department1' },{ id: '2', name: 'department2' }]
}
]
department.forEach( dep => {
console.log(`Department: ${dep.name}`)
models.forEach(model => {
if (model.departments.find(modelDep => dep.id===modelDep.id)) {
console.log(` Model: ${model.name}`)
}
})
})
If you could change your data objects, then your code could be much smoother.
I've changed your data objects slightly by just reducing the departments in a model to be an array of department id's. This code iterates over the departments. For each department it filters the models and iterates over the filtered models to output them to the console. This is lesser code and provides much better performance.
const department =
[
{ id: '1', name: 'department1' },
{ id: '2', name: 'department2' }
]
const models =
[
{
id: '23',
name: 'model1',
departments: ['1']
},
{
id: '54',
name: 'model2',
departments: ['1', '2']
}
]
department.forEach( dep => {
console.log(`Department: ${dep.name}`)
models.filter(model => model.departments.includes(dep.id)).forEach(model => {
console.log(` Model: ${model.name}`)
})
})
There are two solutions.
Using Array.reduce() --> returns an object where the key is department name and value is an array of the names of matching models:
let data1 = models.reduce((res, curr) => {
curr.departments.forEach(dep => {
if (!res[dep.name]) {
res[dep.name] = [curr.name]
} else {
if (!res[dep.name].includes(curr.name)) {
res[dep.name].push(curr.name);
}
}
})
return res;
}, {});
Using map and filter --> returns an array of kind:
[{department: [names of the models]},...]
let data2 = department.map(dep => {
let matchingModels = models.filter(model => {
return model.departments.filter(modDep => {
return modDep.name === dep.name;
}).length > 0;
}).map(mod => {
return mod.name;
});
return {
department: dep.name,
models: matchingModels
}
});
I have this sample data:
const data = [
{
id: 1,
title: 'Sports',
menus: [
{
id: 2,
title: 'Basketball',
menus: [
{
id: 3,
title: 'NBA',
},
{
id: 4,
title: 'NCAA',
},
{
id: 5,
title: 'G-League',
},
],
},
],
},
{
id: 100,
title: 'Names',
menus: [],
},
];
I want to change all the menus keys into children, so the result would be:
const result = [
{
id: 1,
title: 'Sports',
children: [
{
id: 2,
title: 'Basketball',
children: [
{
id: 3,
title: 'NBA',
},
{
id: 4,
title: 'NCAA',
},
{
id: 5,
title: 'G-League',
},
],
},
],
},
{
id: 100,
title: 'Names',
children: [],
},
];
I'm trying with this code:
const replacer = { menus: 'children' };
const transform = useCallback(
(obj) => {
if (obj && Object.getPrototypeOf(obj) === Object.prototype) {
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [replacer[k] || k, transform(v)]));
}
return obj;
},
[replacer]
);
but it only changes the keys at the first level. How can I make it work?
You can use a recursive function that makes use of destructuring:
const replaceKey = arr =>
arr.map(({menus, ...o}) =>
menus ? {...o, children: replaceKey(menus)} : o);
const data = [{id: 1,title: 'Sports',menus: [{id: 2,title: 'Basketball',menus: [{id: 3,title: 'NBA',},{id: 4,title: 'NCAA',},{id: 5,title: 'G-League',},],},],},{id: 100,title: 'Names',menus: [],},];
console.log(replaceKey(data));
To provide the old/new key dynamically, use the following variant:
const replaceKey = (arr, source, target) =>
arr.map(({[source]: v, ...o}) =>
v ? {...o, [target]: replaceKey(v, source, target)} : o);
const data = [{id: 1,title: 'Sports',menus: [{id: 2,title: 'Basketball',menus: [{id: 3,title: 'NBA',},{id: 4,title: 'NCAA',},{id: 5,title: 'G-League',},],},],},{id: 100,title: 'Names',menus: [],},];
console.log(replaceKey(data, "menus", "children"));
This code assumes that values for the given key are arrays. If for some reason their values could be something else, then the code needs a bit more extension:
const data = [{id: 1,title: 'Sports',menus: [{id: 2,title: 'Basketball',menus: [{id: 3,title: 'NBA',},{id: 4,title: 'NCAA',},{id: 5,title: 'G-League',},],},],},{id: 100,title: 'Names',menus: 13,},];
const replaceKey = (arr, source, target) =>
Array.isArray(arr) ? arr.map(({[source]: value, ...o}) =>
value !== undefined ? {...o, [target]: replaceKey(value, source, target)} : o
) : arr;
console.log(replaceKey(data, "menus", "children"));
To see the effect of this code, the value for the very last menus key was changed to 13.
If the object is not big:
let data=[{id:1,title:'Sports',menus:[{id:2,title:'Basketball',menus:[{id:3,title:'NBA',},{id:4,title:'NCAA',},{id:5,title:'G-League',},],},],},{id:100,title:'Names',menus:[],},];
data = JSON.parse(JSON.stringify(data).replace(/"menus"\:/g,'"children":'))
console.log(data)
check this package: paix: that's take original source object and desired keys replacement then return a new object with desired keys, ex:
npm i paix
import { paix } from 'paix';
const data = [
{
id: 1,
title: 'Sports',
menus: [
{
id: 2,
title: 'Basketball',
menus: [
{
id: 3,
title: 'NBA',
},
],
},
],
},
{
id: 100,
title: 'Names',
menus: [],
},
];
const keys_swap = {menus: "children"};
const result = data.map(i => paix(i, keys_swap));
I'm trying to filter a on a nested array inside an array of objects in an Angular app. Here's a snippet of the component code -
var teams = [
{ name: 'Team1', members: [{ name: 'm1' }, { name: 'm2' }, { name: 'm3' }] },
{ name: 'Team2', members: [{ name: 'm4' }, { name: 'm5' }, { name: 'm6' }] },
{ name: 'Team3', members: [{ name: 'm7' }, { name: 'm8' }, { name: 'm9' }] }
];
What I'm trying to achieve is if I search for m5 for example my result should be -
var teams = [
{ name: 'Team1', members: [] },
{ name: 'Team2', members: [{ name: 'm5' }] },
{ name: 'Team3', members: [] }
];
So I've got teams and filteredTeams properties and in my search function I'm doing -
onSearchChange(event: any): void {
let value = event.target.value;
this.filteredTeams = this.teams.map(t => {
t.members = t.members.filter(d => d.name.toLowerCase().includes(value));
return t;
})
}
Now this does work to some extent however because I'm replacing the members it's destroying the array on each call (if that makes sense). I understand why this is happening but my question is what would be the best way to achieve this filter?
you were very close, the only thing that you did wrong was mutating the source objects in teams
basically you can use spread operator to generate a new entry and then return a whole new array with new values.
const teams = [
{ name: 'Team1', members: [{ name: 'm1' }, { name: 'm2' }, { name: 'm3' }] },
{ name: 'Team2', members: [{ name: 'm4' }, { name: 'm5' }, { name: 'm6' }] },
{ name: 'Team3', members: [{ name: 'm7' }, { name: 'm8' }, { name: 'm9' }] }
];
const value = 'm5';
const result = teams.map(t => {
const members = t.members.filter(d => d.name.toLowerCase().includes(value));
return { ...t, members };
})
console.log(result)
Check this. Instead of hard coded m5 pass your value.
const teams = [
{ name: 'Team1', members: [{ name: 'm1' }, { name: 'm2' }, { name: 'm3' }] },
{ name: 'Team2', members: [{ name: 'm4' }, { name: 'm5' }, { name: 'm6' }] },
{ name: 'Team3', members: [{ name: 'm7' }, { name: 'm8' }, { name: 'm9' }] }
];
const filteredTeams = teams.map(team => ({ name: team.name, members: team.members.filter(member => member.name.includes('m5')) }));
console.log(filteredTeams);
You are mutating the original objects, but you could assing new properties to the result object for mapping instead.
var teams = [{ name: 'Team1', members: [{ name: 'm1' }, { name: 'm2' }, { name: 'm3' }] }, { name: 'Team2', members: [{ name: 'm4' }, { name: 'm5' }, { name: 'm6' }] }, { name: 'Team3', members: [{ name: 'm7' }, { name: 'm8' }, { name: 'm9' }] }],
result = teams.map(o => Object.assign(
{},
o,
{ members: o.members.filter(({ name }) => name === 'm5') }
));
console.log(result);
console.log(teams);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Try to seperate your filter function first:
const filterTeamMembers = (teams, filterArr) => {
const useFilter = filterArr.map(x => x.toLowerCase());
return teams.map(team => ({
...team,
members: team.members.filter(member => useFilter.includes(member.name))
}))
};
// =========== And then:
onSearchChange(event: any): void {
let value = event.target.value;
this.filteredTeams = filterTeamMembers(this.teams, [value]);
}