JavaScript: Rebuild Array of Objects using Reduce - javascript

I have an array of objects that I'm trying to rebuild without any success:
const data = [
{
ID: 1,
TemplateName: 'Template 1',
TemplateCategory: 'Category A',
},
{
ID: 2,
TemplateName: 'Template 2',
TemplateCategory: 'Category A',
},
{
ID: 3,
TemplateName: 'Template 3',
TemplateCategory: 'Category B',
},
]
I have the below code which produces the following undesired result:
result = [...data
.reduce((acc, {TemplateCategory, TemplateName, ID}) => {
const group = acc.get(TemplateCategory)
group ? group.options.push(ID, TemplateName) : acc.set(TemplateCategory, {TemplateCategory, "options":[ID, TemplateName]})
return acc
}, new Map)
.values()
]
console.log(result) // undesired result:
[
{
TemplateCategory: 'Category A',
options: [1, 'Template 1', 2, 'Template 2']
},
{
TemplateCategory: 'Category B',
options: [3, 'Template 3']
}
]
I am stuck on trying to convert options to an Array of Objects with value and label as properties. Also im struggling trying to reword TemplateCategory property to label.
My desired result is:
[
{
label: 'Category A',
options: [
{
value: 1,
label: 'Template 1'
},
{
value: 2,
label: 'Template 2'
}
]
},
{
label: 'Category B',
options: [
{
value: 3,
label: 'Template 3'
}
]
}
]
TIA

Like this
const data = [
{
ID: 1,
TemplateName: 'Template 1',
TemplateCategory: 'Category A',
},
{
ID: 2,
TemplateName: 'Template 2',
TemplateCategory: 'Category A',
},
{
ID: 3,
TemplateName: 'Template 3',
TemplateCategory: 'Category B',
},
]
const result = [...data
.reduce((acc, {TemplateCategory, TemplateName, ID}) => {
const group = acc.get(TemplateCategory)
group ? group.options.push({value: ID, label: TemplateName}) : acc.set(TemplateCategory, {label: TemplateCategory, "options":[{value: ID, label: TemplateName}]})
return acc
}, new Map)
.values()
]
console.log(result) // undesired result:

Related

Can you transform a list to a tree where items in the list have children but no depth or parent?

Data looks as follows:
Each node has a unique id (
Nodes have a children key which is either null or an array of ids.
Nodes can have one parent
Nodes do not have a parent or depth reference
Input:
const items = [
{
id: 1,
name: 'Item 1',
children: [ 2, 3 ]
},
{
id: 2,
name: 'Item 2',
children: null
},
{
id: 3,
name: 'Item 3',
children: null
},
{
id: 4,
name: 'Item 4',
children: [ 5 ]
},
{
id: 5,
name: 'Item 5',
children: [ 6 ]
},
{
id: 6,
name: 'Item 6',
children: null
},
}
]
Expected Output:
const tree = [
{
id: 1,
name: 'Item 1',
children: [
{
id: 2,
name: 'Item 2',
children: null
},
{
id: 3,
name: 'Item 3',
children: null
},
]
},
{
id: 4,
name: 'Item 4',
children: [
{
id: 5,
name: 'Item 5',
children: [
{
id: 6,
name: 'Item 6',
children: null
}
]
}
]
}
]
If this is in fact possible, would love to 1) see how it is done and 2) see if there are any libraries that handle this use case.
The resulting structure is more a forest than a tree, as not all nodes are connected and you have multiple "roots".
You can first key the nodes by their id in a Map, and then iterate all children arrays to replace their contents by the corresponding items found in the Map. At the same time keep track of all the children, so that at the end you can identify which items are not children, and therefore belong in the result array:
const items = [{id: 1,name: 'Item 1',children: [ 2, 3 ]},{id: 2,name: 'Item 2',children: null},{id: 3,name: 'Item 3',children: null},{id: 4,name: 'Item 4',children: [ 5 ]},{id: 5,name: 'Item 5',children: [ 6 ]},{id: 6,name: 'Item 6',children: null},];
const map = new Map(items.map(item => [item.id, item]));
const children = new Set;
for (const item of items) {
if (!item.children) continue;
for (const id of item.children) children.add(id);
item.children = item.children?.map(id => map.get(id));
}
const forest = items.filter(({id}) => !children.has(id));
console.log(forest);

Changing nested object's structure

I want to convert an object from one format to another. So far my attempts at doing this recursively failed; either I'm getting a maximum stack exception or I'm unable to iterate over all paths.
Let's assume we have an object that lists questions and their answers. There may be N questions and M answers.
Object at start:
var before = {
item: 'Question 1',
id: '1',
type: 'Question',
items: [
{
item: 'Answer 1',
id: '1.1',
type: 'Answer',
items:
[
{
item: 'Question 2',
id: '1.1.1',
type: 'Question',
items: [
{
item: 'Answer 2.1',
id: '1.1.1.1',
type: 'Answer'
},
{
item: 'Answer 2.2',
id: '1.1.1.2',
type: 'Answer'
}
]
}
// ...
]
}, {
item: 'Answer 1',
id: '1.2',
type: 'Answer',
items:
[
{
item: 'Question 3',
id: '1.2.1',
type: 'Question',
items: [
{
item: 'Answer 3.1',
id: '1.2.1.1',
type: 'Answer'
},
{
item: 'Answer 3.2',
id: '1.2.1.2',
type: 'Answer'
}
]
}
// ...
]
}
// ...
]
}
Object how it should look like (wrap all in 'items' array; change key names 'item' to 'title', 'id' to 'key', remove 'type', add 'color' depending on 'type'):
var after = {
items: [
{
title: 'Question 1',
key: '1',
color: 'Red',
items: [
{
title: 'Answer 1',
key: '1.1',
color: 'Blue',
items:
[
{
title: 'Question 2',
key: '1.1.1',
color: 'Red',
items: [
{
title: 'Answer 2.1',
key: '1.1.1.1',
color: 'Blue'
},
{
title: 'Answer 2.2',
key: '1.1.1.2',
color: 'Blue'
}
]
}
// ...
]
}, {
title: 'Answer 1',
key: '1.2',
color: 'Blue',
items:
[
{
title: 'Question 3',
key: '1.2.1',
color: 'Red',
items: [
{
title: 'Answer 3.1',
key: '1.2.1.1',
color: 'Blue'
},
{
title: 'Answer 3.2',
key: '1.2.1.2',
color: 'Blue'
}
]
}
// ...
]
}
// ...
]
}
]
}
It seems easy enough, but I can't get it to work. This is how I tried to iterate:
function iter(o) {
for(let k in o) {
if (!(['item', 'items', 'id'].includes(k))) // My object contains a few more keys I don't want to go down further into
continue
if (o[k] !== null && typeof o[k] === 'object') {
iter(o[k]); // Max stack exception
return;
}
}
};
Thank you very much!
You could map the objects and rename the keys and map nested items.
const
iter = ({ item: title, id: key, type, items, ...o }) => ({
title,
key,
color: 'color' + key,
...o,
...(items && { items: items.map(iter) })
}),
before = { item: 'Question 1', id: '1', type: 'Question', items: [{ item: 'Answer 1', id: '1.1', type: 'Answer', items: [{ item: 'Question 2', id: '1.1.1', type: 'Question', items: [{ item: 'Answer 2.1', id: '1.1.1.1', type: 'Answer' }, { item: 'Answer 2.2', id: '1.1.1.2', type: 'Answer' }] }] }, { item: 'Answer 1', id: '1.2', type: 'Answer', items: [{ item: 'Question 3', id: '1.2.1', type: 'Question', items: [{ item: 'Answer 3.1', id: '1.2.1.1', type: 'Answer' }, { item: 'Answer 3.2', id: '1.2.1.2', type: 'Answer' }] }] }] },
result = [before].map(iter);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can achieve this using map, I wrote an simple test to show my point here
this is the important part of the code
function rename(item: any) {
return {
title: item.item,
key: item.id,
color: item.type === 'Question' ? 'red' : 'blue',
items: item.items?.map(rename)
}
}
console.log(items.map(rename))
Of course if you're using typescript, change any to the appropriate type and pay attention that I'm using ? operator which will not work with javascript, so you could do something like
...
items: item.items ? item.items.map(rename) : undefined
...

Set index value in nested map

I have problem when set value in nested map, the assigned value only take the last index value. Is that something I do wrong or I miss?Thank you
Here is my data:
const items = [{
id: 'item1'
}, {
id: 'item2'
}]
const itemDetails = [{
name: 'data A',
class: 'A'
}, {
name: 'data B',
class: 'B'
}, {
name: 'data C',
class: 'C'
}]
The result I expect is:
[
[
{ name: 'data A', class: 'A', itemIndex: 0, itemId: 'item1' },
{ name: 'data B', class: 'B', itemIndex: 0, itemId: 'item1' },
{ name: 'data C', class: 'C', itemIndex: 0, itemId: 'item1' }
],
[
{ name: 'data A', class: 'A', itemIndex: 1, itemId: 'item2' },
{ name: 'data B', class: 'B', itemIndex: 1, itemId: 'item2' },
{ name: 'data C', class: 'C', itemIndex: 1, itemId: 'item2' }
]
]
But I got this result using nested map:
[
[
{ name: 'data A', class: 'A', itemIndex: 1, itemId: 'item2' },
{ name: 'data B', class: 'B', itemIndex: 1, itemId: 'item2' },
{ name: 'data C', class: 'C', itemIndex: 1, itemId: 'item2' }
],
[
{ name: 'data A', class: 'A', itemIndex: 1, itemId: 'item2' },
{ name: 'data B', class: 'B', itemIndex: 1, itemId: 'item2' },
{ name: 'data C', class: 'C', itemIndex: 1, itemId: 'item2' }
]
]
My Code:
const result = items.map((item, itemIdx) => {
return itemDetails.map(detail => {
detail.itemIndex = itemIdx
detail.itemId = item.id
return detail
})
})
Less code:
const result = items.map((item, itemIndex) =>
itemDetails.map(detail => ({...detail, itemIndex, itemId: item.id})))
Please first create a copy of the original detail and then mutate it.
This should work fine:
const result = items.map((item, itemIdx) => {
return itemDetails.map(detail => {
const newDetail = {...detail}
newDetail.itemIndex = itemIdx
newDetail.itemId = item.id
return newDetail;
})
})
var resArr=[];
// I am using .length in loops if your data is large you can precalculate the values // intovariables and use them
for(var i=0;i<items.length;i++){
const id = items[i]["id"];
var temparr=[]
for(var j=0;j<itemDetails.length;j++){
let obj={"name":itemDetails[j]["name"],"class":itemDetails[j]["class"],"itemIndex":i,"itemId":id}
temparr.push(obj);}
resArr.push(temparr)
}
console.log(resArr);

Count duplicates in an array and return new array with a key value derived from another array in Javascript

Following on from my previous question, I'd like to change and extend the capability of what was suggested.
Here's the data I've got:
const things = [
{
id: 1,
title: 'Something',
categoryId: 1
},
{
id: 2,
title: 'Another thing',
categoryId: 1
},
{
id: 3,
title: 'Yet another thing',
categoryId: 2
},
{
id: 4,
title: 'One more thing',
categoryId: 4
},
{
id: 5,
title: 'Last thing',
categoryId: 4
}
]
const categories = [
{
id: 1,
title: 'Category 1'
},
{
id: 2,
title: 'Category 2'
},
{
id: 4,
title: 'Category 3'
}
]
Previously I've been shown how to do something along these lines:
const duplicatesCountWithTitle = (things, categories) => {
const thingsReduced = things.reduce((hash, { categoryId }) => {
hash[categoryId] = (hash[categoryId] || 0) + 1
return hash
}, {})
}
As you'd be able to tell, the problem with this is that it actually returns a new object, and not a new array. Also I'd like to join the categoryTitle from the categories array with the results of the duplicated count from the things array, based on the categoryId matching the id in categories.
// currently the above returns an object in the structure of:
// {
// 1: 2,
// 2: 1,
// 4: 2
// }
// what I'm after is an array like this:
// [
// { 'Category 1': 2 },
// { 'Category 2': 1 },
// { 'Category 3': 2 }
// ]
Thanks in advance, again.
Something like this?
const newArr = categories.map(category => {
const count = things.filter(thing => thing.categoryId === category.id).length;
return { [category.title]: count }
});
console.log(newArr);
https://jsfiddle.net/f3x6m12j/
You could take a Map for the relation of id and title.
const
duplicatesCountWithTitle = (things, categories) => things.reduce((hash, { categoryId }) => {
hash[categories.get(categoryId)] = (hash[categories.get(categoryId)] || 0) + 1
return hash;
}, {}),
things = [{ id: 1, title: 'Something', categoryId: 1 }, { id: 2, title: 'Another thing', categoryId: 1 }, { id: 3, title: 'Yet another thing', categoryId: 2 }, { id: 4, title: 'One more thing', categoryId: 4 }, { id: 5, title: 'Last thing', categoryId: 4 }],
categories = [{ id: 1, title: 'Category 1' }, { id: 2, title: 'Category 2' }, { id: 4, title: 'Category 3' }],
result = duplicatesCountWithTitle(
things,
categories.reduce((m, { id, title }) => m.set(id, title), new Map)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Remove object from array in another array

I have the following data structure
menu: [ { id: 1, title: 'Test 1', children: [] },
{ id: 2, title: 'Test 2', children: [
{ id: 5, title: 'Test 5', children: [] },
{ id: 6, title: 'Test 6', children: [] },
{ id: 7, title: 'Test 7', children: [] },
{ id: 8, title: 'Test 8', children: [] },
] },
{ id: 3, title: 'Test 3', children: [
{ id: 9, title: 'Test 9', children: [] },
{ id: 10, title: 'Test 10', children: [] },
{ id: 11, title: 'Test 11', children: [] },
{ id: 12, title: 'Test 12', children: [] },
] },
{ id: 4, title: 'Test 4', children: [] },
]
How can remove object with Title 'Test 5'? Or from sub array in children arr?
onDeleteClick(item) {
const menuCopy = JSON.parse(JSON.stringify(this.menu));
const index = menuCopy.indexOf(item);
if (index !== -1) {
menuCopy.splice(index, 1);
} else {
menuCopy.map((el) => {
if (el.children.length) {
el.children.map((child) => {
if (child.Id === item.Id) {
console.log(child);
}
});
}
});
}
this.setMenu(menuCopy);
}
I am stuck at this point. I think that here should be used recursion but i have no idea how to implement this.
const menu = [ { id: 1, title: 'Test 1', children: [] },
{ id: 2, title: 'Test 2', children: [
{ id: 5, title: 'Test 5', children: [] },
{ id: 6, title: 'Test 6', children: [
{ id: 5, title: 'Test 5', children: [] },
{ id: 7, title: 'Test 7', children: [] },
{ id: 8, title: 'Test 8', children: [] }
] },
{ id: 7, title: 'Test 7', children: [] },
{ id: 8, title: 'Test 8', children: [] },
] },
{ id: 3, title: 'Test 3', children: [
{ id: 9, title: 'Test 9', children: [] },
{ id: 10, title: 'Test 10', children: [] },
{ id: 11, title: 'Test 11', children: [] },
{ id: 12, title: 'Test 12', children: [] },
] },
{ id: 4, title: 'Test 4', children: [] },
];
const excludeChildrenFromTitle = (arr, excludedChildTitle) => {
return arr.map((item) => {
const children = excludeChildrenFromTitle(item.children.filter((child) => child.title !== excludedChildTitle), excludedChildTitle);
return {
...item,
children
}
});
};
console.log(excludeChildrenFromTitle(menu, 'Test 5'))
Using a simple map for the whole menu array and then filtering every children array from each menu item can do the job.
I have updated the answer to remove the excluded child from sub array too.
You can filter first each first-level element and then second-level with map:
var l = [{ id: 1, title: 'Test 1', children: [] }, { id: 2, title: 'Test 2', children: [ { id: 5, title: 'Test 5', children: [] }, { id: 6, title: 'Test 6', children: [] }, { id: 7, title: 'Test 7', children: [] }, { id: 8, title: 'Test 8', children: [] }, ] }, { id: 3, title: 'Test 3', children: [ { id: 9, title: 'Test 9', children: [] }, { id: 10, title: 'Test 10', children: [] }, { id: 11, title: 'Test 11', children: [] }, { id: 12, title: 'Test 12', children: [] }, ] }, { id: 4, title: 'Test 4', children: [] },
{ id: 5, title: 'Test 5', children: [] }, ];
const removeTitleByValue = (arr, titleValue) => {
return arr
.filter(e => e.title !== titleValue)
.map((e2) => {
const children = e2.children.filter((ch) => ch.title !== titleValue);
return { ...e2, children }
});
};
console.log(removeTitleByValue(l, 'Test 5'))

Categories