Create an array of objects with values from another object - javascript

I have an object looking like this
const item = {
id: 123,
type: 'book',
sections: [{
type: 'section',
id: '456',
index: 1,
lessons: [{
type: 'lesson',
id: 789,
index: 1
},
{
type: 'lesson',
id: 999,
index: 2
}
]
}, {
type: 'section',
index: 2,
id: 321,
lessons: [{
type: 'lesson',
id: 444,
index: 1
},
{
type: 'lesson',
id: 555,
index: 2
}
]
}]
}
It should be assumed that there are more objects in sections and lessons array. I want to create a new object like this
result = [{
section: 456,
lessons: [789, 999]
}, {
section: 321,
lessons: [444, 555]
}]
I tried this loop but this just pushes indexes and not lesson's ids
let obj = {};
let sectionWithLessons = [];
let lessons = []
for (const i in item.sections) {
obj = {
sectionId: item.sections[i].id,
lessonIds: item.sections[i].lessons.map((lesson) => {
return lessons.push(lesson.id)
}),
};
sectionWithLessons.push(obj);
}
console.log(sectionWithLessons);
How can i do this correctly and preferably with good performance in consideration?

I believe the best/shortest thing is to use the map function, like:
const result2 = item.sections.map(({id, lessons}) => ({
id,
lessons: lessons.map(({id: lessionId}) => lessionId)
}))

I would suggest using Array.map() to convert the item sections to the desired result.
We'd convert each section into an object with a section value and lessons array.
To create the lessons array, we again use Array.map() to map each lesson to a lesson id.
const item = { id: 123, type: 'book', sections: [{ type: 'section', id: '456', index: 1, lessons: [{ type: 'lesson', id: 789, index: 1 }, { type: 'lesson', id: 999, index: 2 } ] }, { type: 'section', index: 2, id: 321, lessons: [{ type: 'lesson', id: 444, index: 1 }, { type: 'lesson', id: 555, index: 2 } ] }] }
const result = item.sections.map(({ id, lessons }) => {
return ({ section: +id, lessons: lessons.map(({ id }) => id) })
});
console.log('Result:', result);
.as-console-wrapper { max-height: 100% !important; }

Related

How can I access this property in my dictionary in js?

I thought I understood how to loop through a dictionary, but my loop is wrong. I try to access the name of each sub item but my code does not work.
Here is what I did:
list = [
{
title: 'Groceries',
items: [
{
id: 4,
title: 'Food',
cost: 540 ,
},
{
id: 5,
title: 'Hygiene',
cost: 235,
},
{
id: 6,
title: 'Other',
cost: 20,
},
],
}];
function calculateCost(){
let total = 0;
Object.keys(list).forEach((k) => { for (i in k.items) { total += i.data; } });
console.log(total);
return total;
}
Your list is an array includes 1 object and this object has two properties title and items the items here is an array of objects each one of these objects has property cost so to calculate the total cost you need to loop through items array, here is how you do it:
let list = [
{
title: 'Groceries',
items: [
{
id: 4,
title: 'Food',
cost: 540 ,
},
{
id: 5,
title: 'Hygiene',
cost: 235,
},
{
id: 6,
title: 'Other',
cost: 20,
},
],
}];
function calculateCost(){
let total = 0;
list[0].items.forEach(el => {
total += el.cost;
})
console.log(total)
return total;
}
calculateCost();
Your list is an Array, not an Object.
Instead of Object.keys() use Array.prototype.reduce:
const calculateCost = (arr) => arr.reduce((tot, ob) =>
ob.items.reduce((sum, item) => sum + item.cost, tot), 0);
const list = [
{
title: 'Groceries',
items: [
{id: 4, title: 'Food', cost: 10},
{id: 5, title: 'Hygiene', cost: 20},
{id: 6, title: 'Other', cost: 30}
]
}, {
title: 'Other',
items: [
{id: 8, title: 'Scuba gear', cost: 39}
],
}
];
console.log(calculateCost(list)); // 99
Expanding on #Roko's and #mmh4all's answers, the following code adds several verification statements to handle cases where a deeply nested property in your data is not what you expect it to be.
const calculateCost = (orders) => {
let listOfCosts = [];
// For each 'order' object in the 'orders' array,
// add the value of the 'cost' property of each item
// in the order to 'listOfCosts' array.
orders.forEach(order => {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
if (!Array.isArray(order.items)) { return; }
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat
const orderCostArr = order.items.map(item =>
isNaN(item.cost) ? 0 : parseFloat(item.cost, 10));
if (orderCostArr.length === 0) { return; }
// Concatenate 'orderCostArr' to the 'listOfCosts' array
//listOfCosts = listOfCosts.concat(orderCostArry);
// Alternate approach is to use the spread syntax (...) to
// push the items in the array returned by 'order.items.map()'
// into the 'listOfCosts' array.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
listOfCosts.push(...orderCostArr);
});
// Use the 'reduce' method on the 'listOfCosts' array
// to get the total cost.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
const totalCost = listOfCosts.reduce(
(accumulator, currentValue) => accumulator + currentValue, 0);
return totalCost;
};
const list = [
{
title: 'Groceries',
items: [
{ id: 4, title: 'Food', cost: 10 },
{ id: 3, title: 'Baked goods', cost: 20 },
{ id: 5, title: 'Hygiene', cost: 0 },
{ id: 6, title: 'Other' }
]
}, {
title: 'Gear',
items: {},
}, {
title: 'Accessories',
items: [],
}, {
title: 'Bags',
}, {
title: 'Other',
items: [
{ id: 10, title: 'Scuba gear', cost: "5" },
{ id: 8, title: 'Scuba gear', cost: "err" },
{ id: 9, title: 'Scuba gear', cost: 59 }
],
}
];
console.log(calculateCost(list)); // 94

Group array of objects by multiple nested values

I have an array of objects which presents tasks. These tasks are categorized (primary / secondary category).
let tasks = [
{
id: 1,
name: 'Cleanup desk',
primary_category: {
id: 1,
name: 'Indoor'
},
secondary_category: {
id: 2,
name: 'Surfaces'
}
},
{
id: 2,
name: 'Cleanup office floors',
primary_category: {
id: 1,
name: 'Indoor'
},
secondary_category: {
id: 3,
name: 'Ground'
}
},
{
id: 3,
name: 'Water plants',
primary_category: {
id: 2,
name: 'Outdoor'
},
secondary_category: {
id: 3,
name: 'Irrigation'
}
}
];
I now try to create a categories accordion in my frontend and therefore need to group my array differently. The structure should look like:
1) primary category
> secondary category
> tasks
> secondary category
> tasks
2) primary category
> secondary category
> tasks
Therefore I'm trying to achieve a structure similar to this:
let tasks_categorized = [
{
id: 1,
name: 'Indoor',
secondary_categories: [
{
id: 2,
name: 'Surfaces',
tasks: [
{
id: 1,
name: 'Cleanup desk'
}
]
},
{
id: 3,
name: 'Ground',
tasks: [
{
id: 2,
name: 'Cleanup office floors'
}
]
}
]
},
{
id: 2,
name: 'Outdoor',
secondary_categories: [
{
id: 3,
name: 'Irrigation',
tasks: [
{
id: 3,
name: 'Water plants'
}
]
}
]
}
];
I tried using groupBy by lodash but this does not allow grouping by multiple nested key-value pairs. Does anybody know an approach to solve this?
Thank you in advance!
The following provided approach is going to achieve the expected result within a single reduce cycle without any further nested loops.
It does so by implementing a reducer function which creates and/or aggregates at time a prioritized category task while iterating another task array. But most importantly it keeps track of a task item's related primary and secondary categories via a Map based lookup. This lookup reference together with a result array are properties of this function's return value which has to be partly provided as the reduce method's initial value as follows ... { result: [] }.
function createAndAggregatePrioritizedCategoryTask(
{ lookup = new Map, result }, item
) {
const { primary_category, secondary_category, ...taskRest } = item;
const { id: primaryId, name: primaryName } = primary_category;
const { id: secondaryId, name: secondaryName } = secondary_category;
const primaryKey = [primaryId, primaryName].join('###');
const secondaryKey = [primaryKey, secondaryId, secondaryName].join('###');
let primaryCategory = lookup.get(primaryKey);
if (!primaryCategory) {
// create new primary category item.
primaryCategory = {
id: primaryId,
name: primaryName,
secondary_categories: [],
};
// store newly created primary category reference in `lookup`.
lookup.set(primaryKey, primaryCategory);
// push newly created primary category reference to `result`.
result.push(primaryCategory);
}
let secondaryCategory = lookup.get(secondaryKey);
if (!secondaryCategory) {
// create new secondary category item.
secondaryCategory = {
id: secondaryId,
name: secondaryName,
tasks: [],
};
// store newly created secondary category reference in `lookup`.
lookup.set(secondaryKey, secondaryCategory);
// push newly created secondary category reference into the
// `secondary_categories` array of its related primary category.
primaryCategory
.secondary_categories
.push(secondaryCategory);
}
// push the currently processed task-item's rest-data as
// item into the related secondary category's `task` array.
secondaryCategory
.tasks
.push(taskRest);
return { lookup, result };
}
let tasks = [{
id: 1,
name: 'Cleanup desk',
primary_category: { id: 1, name: 'Indoor' },
secondary_category: { id: 2, name: 'Surfaces' },
}, {
id: 2,
name: 'Cleanup office floors',
primary_category: { id: 1, name: 'Indoor' },
secondary_category: { id: 3, name: 'Ground' },
}, {
id: 3,
name: 'Water plants',
primary_category: { id: 2, name: 'Outdoor' },
secondary_category: { id: 3, name: 'Irrigation' },
}];
const { result: tasks_categorized } = tasks
.reduce(createAndAggregatePrioritizedCategoryTask, { result: [] });
console.log({ tasks_categorized });
.as-console-wrapper { min-height: 100%!important; top: 0; }
You could take a dynamic approach with an array of arrays with functions and keys for the nested arrays.
const
tasks = [{ id: 1, name: 'Cleanup desk', primary_category: { id: 1, name: 'Indoor' }, secondary_category: { id: 2, name: 'Surfaces' } }, { id: 2, name: 'Cleanup office floors', primary_category: { id: 1, name: 'Indoor' }, secondary_category: { id: 3, name: 'Ground' } }, { id: 3, name: 'Water plants', primary_category: { id: 2, name: 'Outdoor' }, secondary_category: { id: 3, name: 'Irrigation' } }],
groups = [
[o => o, 'primary category'],
[o => o.primary_category, 'secondary category'],
[o => o.secondary_category, 'tasks']
],
result = tasks.reduce((r, o) => {
groups.reduce((parent, [fn, children]) => {
const { id, name } = fn(o);
let item = (parent[children] ??= []).find(q => q.id === id)
if (!item) parent[children].push(item = { id, name });
return item;
}, r);
return r;
}, {})[groups[0][1]];
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

fillter arrays of objects

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
}
});

Javascript How to get all values for a property from an Array of Objects?

Hello fellow Javascript developers, I hope you're all having a good day.
I'm trying to get all dynamically set values for certain properties of object elements from an array for my filter. For e.g.
var data = [
{id: 0, category: "RINGS", type: "CH"},
{id: 1, category: "NECKLACE", type: "CHE"},
{id: 2, category: "PENDANT", type: "CH"},
{id: 3, category: "BRACELET", type: "CH"},
{id: 4, category: "RINGS", type: "HBR"},
{id: 5, category: "PENDANT", type: "CHE"},
];
and exmaple array for how my data comes from the api. As you can see the two properties category & type that I know will always remain constant but their values may change based on user input data.
I want to set all available values for these two object props for my filter. How do I get all the values for a certain prop to get myself an array of values for a certain property which then I can assign to my Dropdown in React Native.
Result:
var category = [
{ name: "Rings", id: "RINGS"},
{ name: "Necklace", id: "NECKLACE"},
{ name: "Pendant", id: "PENDANT"},
{ name: "Bracelet", id: "BRACELET"},
]
and
var type = [
{ name: "CH", id: "CH" },
{ name: "CHE", id: "CHE" },
{ name: "HBR", id: "HBR" },
]
and then these two arrays are basically passed to the filter which will then be used to sort the data. I assume it's not too complex, but I'm a total beginner in javascript so bear with me. Thank You.
var data = [
{ id: 0, category: 'RINGS', type: 'CH' },
{ id: 1, category: 'NECKLACE', type: 'CHE' },
{ id: 2, category: 'PENDANT', type: 'CH' },
{ id: 3, category: 'BRACELET', type: 'CH' },
{ id: 4, category: 'RINGS', type: 'HBR' },
{ id: 5, category: 'PENDANT', type: 'CHE' }
];
const categories = [], types = [];
data.forEach((item) => {
if (!categories.includes(item.category)) {
categories.push(item.category);
}
if (!types.includes(item.type)) {
types.push(item.type);
}
});
const category = categories.map((item) => ({
name: item.toLowerCase(),
id: item
}));
const type = types.map((item) => ({ name: item, id: item }));
console.log(category);
console.log(type);
There are atleast 2 ways you can do this:
Using Array.includes. As #baymax mentioned above, you can filter through the array using includes.
Using Set in Javascript. Check out the code.
const data = [
{id: 0, category: "RINGS", type: "CH"},
{id: 1, category: "NECKLACE", type: "CHE"},
{id: 2, category: "PENDANT", type: "CH"},
{id: 3, category: "BRACELET", type: "CH"},
{id: 4, category: "RINGS", type: "HBR"},
{id: 5, category: "PENDANT", type: "CHE"},
];
// by includes
const uniqueCategories = [];
const uniqueTypes = []
data.forEach(entry => {
if (!uniqueCategories.includes(entry.category)){
uniqueCategories.push(entry.category)
}
if (!uniqueTypes.includes(entry.type)){
uniqueTypes.push(entry.type)
}
})
const categoriesMap = uniqueCategories.map(category => ({name: category.toLowerCase(), id: category}));
const typesMap = uniqueTypes.map(typeEntry => ({name: typeEntry, id: typeEntry}));
// by set
const categoriesSet = new Set()
const typesSet = new Set()
data.forEach(entry => {
categoriesSet.add(entry.category);
typesSet.add(entry.type);
});
const uniqueCategoriesFromSet = Array.from(categoriesSet).map(category => ({name: category.toLowerCase(), id: category}));
const uniqueTypesFromSet = Array.from(typesSet).map(typeEntry => ({name: typeEntry, id: typeEntry}));
console.log(uniqueCategoriesFromSet, uniqueTypesFromSet);
console.log(categoriesMap, typesMap)

Flatten a deeply nested array with objects and arrays

I have an array of objects that contain another array with objects. The nesting is four levels deep.
The structure of the array is:
[
{
title: 'Title',
type: 'section',
links: [
{
label: 'Label',
id: 'id_1',
links: [
{
title: 'Title',
type: 'section',
links: [
{
label: 'Label',
id: 'id_2',
links: [
{
label: 'Label',
id: 'id_3',
links: [],
}
]
}
]
},
{
title: 'Other title',
type: 'section',
links: [
{
label: 'Label',
id: 'id_4',
links: [],
}
]
}
]
}
]
}
]
I want to have a flattened array with the id's of the link arrays that contain links (they are parents of submenu's).
So the desired outcome is like:
["id_1", "id_2"]
I have tried to get the outcome with this function taken from MDN:
flatDeep(arr, d = 1) {
return d > 0
? arr.reduce((acc, val) =>
acc.concat(Array.isArray(val.links)
? this.flatDeep(val.links, d - 1)
: val.links), [])
: arr.slice();
}
This gives me an empty array.
Use Array.flatMap(). Destructure each object and use an empty array as default for missing id values. Concat the id and the result of flattening the links recursively.
const flattenIds = arr => arr.flatMap(({ id = [], links }) =>
[].concat(id, flattenIds(links))
);
const data = [{ title: 'Title', type: 'section', links: [{ label: 'Label', id: 'id_1', links: [{ title: 'Title', type: 'section', links: [{ label: 'Label', id: 'id_2', links: [{ label: 'Label', id: 'id_3', links: [] }] }] }, { title: 'Other title', type: 'section', links: [{ label: 'Label', id: 'id_4', links: [] }] }] }] }];
const result = flattenIds(data);
console.log(result);
You could get a flat array with a recursion and a check for id for missing property.
const
getId = ({ id, links }) => [
...(id === undefined ? [] : [id]),
...links.flatMap(getId)
],
data = [{ title: 'Title', type: 'section', links: [{ label: 'Label', id: 'id_1', links: [{ title: 'Title', type: 'section', links: [{ label: 'Label', id: 'id_2', links: [{ label: 'Label', id: 'id_3', links: [] }] }] }, { title: 'Other title', type: 'section', links: [{ label: 'Label', id: 'id_4', links: [] }] }] }] }],
result = data.flatMap(getId);
console.log(result);
Here is a non-recursive version.
const data = [{title:'Title',type:'section',links:[{label:'Label',id:'id_1',links:[{title:'Title',type:'section',links:[{label:'Label',id:'id_2',links:[{label:'Label',id:'id_3',links:[]}]}]},{title:'Other title',type:'section',links:[{label:'Label',id:'id_4',links:[]}]}]}]}];
const stack = data.slice();
const result = [];
let obj;
while (obj = stack.shift()) {
if ("id" in obj && obj.links.length > 0) result.push(obj.id);
stack.push(...obj.links);
}
console.log(result);
This uses breath first, but can easily be changed into depth first. You'll only have to change the stack.push call into stack.unshift.
For a more detailed explanation about the two, check out Breadth First Vs Depth First.
var array = JSON.parse('[{"title":"Title","type":"section","links":[{"label":"Label","id":"id_1","links":[{"title":"Title","type":"section","links":[{"label":"Label","id":"id_2","links":[{"label":"Label","id":"id_3","links":[]}]}]},{"title":"Other title","type":"section","links":[{"label":"Label","id":"id_4","links":[]}]}]}]}]');
arr = [];
while(array.length != 0) {
var ob1 = array.splice(0,1)[0];
for(var ob2 of ob1.links) {
if (ob2.links.length !== 0) {
arr.push(ob2.id);
array = array.concat(ob2.links);
}
}
}
console.log(arr);
Here's the output as you requested:
[
"id_1",
"id_2"
]
I think recursive function will simplify. (recursively look for lists array and push the id into res).
const data = [
{
title: "Title",
type: "section",
links: [
{
label: "Label",
id: "id_1",
links: [
{
title: "Title",
type: "section",
links: [
{
label: "Label",
id: "id_2",
links: [
{
label: "Label",
id: "id_3",
links: []
}
]
}
]
},
{
title: "Other title",
type: "section",
links: [
{
label: "Label",
id: "id_4",
links: []
}
]
}
]
}
]
}
];
const res = [];
const ids = data => {
data.forEach(item => {
if ("id" in item) {
res.push(item.id);
}
if (item.links) {
ids(item.links);
}
});
};
ids(data);
console.log(res);

Categories

Resources