Changing nested object's structure - javascript

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
...

Related

How do I create an array of objects with a nested array based on a similar key?

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

Returning array object by filtering it's nested size variants

I have the list of products that i want to filter by their varriants.size
Staring point, data I'm receiving:
const t1 = [
{
name: 'Product 1',
variants: [
{ size: 'sm', sku: '1' },
{ size: 'md', sku: '2' },
],
},
{
name: 'Product 2',
variants: [{ size: 'lg', sku: '4' }],
},
{
name: 'Product 3',
variants: [
{ size: 'sm', sku: '5' },
{ size: 'lg', sku: '6' },
],
},
{
name: 'Product 4',
variants: [{ size: 'sm', sku: '7' }],
},
]
By using ['sm', 'md'] I want to filter above object and return this result
End goal / expected results
const arr = [
{
name: 'Product 2',
variants: [{ size: 'lg', sku: '4' }],
},
{
name: 'Product 3',
variants: [{ size: 'lg', sku: '6' }],
},
]
What I've tried so far but not getting full data / missing properties.
const filter = ['sm', 'md']
const arr = t1.map((e) => {
const filter = e.variants.filter((f) => {
return filter.includes(f.size)
})
return filter
})
But only getting varriants object, rest of the data is missing.
This screenshot is bad example, this one is only filtering ['sm'] but in this case I have multiple filter option ['sm', 'md']
const
t1 = [
{ name: 'Product 1', variants: [{ size: 'sm', sku: '1' }, { size: 'md', sku: '2' }] },
{ name: 'Product 2', variants: [{ size: 'lg', sku: '4' }] },
{ name: 'Product 3', variants: [{ size: 'sm', sku: '5' }, { size: 'lg', sku: '6' }] },
{ name: 'Product 4', variants: [{ size: 'sm', sku: '7' }] }
],
filter = ['sm', 'md'];
const arr = t1
// filter t1 elements variants
.map(e => ({
...e,
variants: e.variants.filter(({ size }) => !filter.includes(size))
}))
// filter resulting elements with no variants left
.filter(({ variants }) => variants.length);
console.log(arr);
Filter each variants subarray by whether the size you want is included, then filter the whole t1 array by whether the subarray contains items.
const t1 = [
{
name: 'Product 1',
variants: [
{ size: 'sm', sku: '1' },
{ size: 'md', sku: '2' },
],
},
{
name: 'Product 2',
variants: [{ size: 'lg', sku: '4' }],
},
{
name: 'Product 3',
variants: [
{ size: 'sm', sku: '5' },
{ size: 'lg', sku: '6' },
],
},
{
name: 'Product 4',
variants: [{ size: 'sm', sku: '7' }],
},
];
const filterBy = ['sm', 'md'];
for (const obj of t1) {
obj.variants = obj.variants.filter(
subobj => !filterBy.includes(subobj.size)
);
}
const filteredInput = t1.filter(obj => obj.variants.length);
console.log(filteredInput);

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

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'))

How to get number of items in array which have the same "parent" element?

I gave an array of items which looks like this
const array = [{
type: 'section',
name: 'name 1',
id: '1'
},
{
type: 'item',
name: 'item 1',
data: {
section: {
name: 'name 1',
id: '1'
}
}
},
{
type: 'item',
name: 'item 2',
data: {
section: {
id: '1',
name: 'name 1'
}
}
},
{
type: 'section',
name: 'name 2',
id: '3'
},
{
type: 'item',
name: 'item 1',
data: {
section: {
id: '3',
name: 'name 2'
}
}
},
{
type: 'section',
name: 'name 3'
}, {
type: 'section',
name: 'name 4'
},
]
I need to count and add to each object of type section the number of items that it has (numberOfItems: 3). The array is built that way that each section has its items follow it.
I also have a separate array of items which basically looks like that
const items = [{
'item_id: {
...
section: {
id: 'section_id'
name: 'section_name'
}
}]
I'm not really sure how to even start.
If I have understood your problem correctly then this code should do it:
const array = [
{
type: 'section',
name: 'name 1',
id: '1'
},
{
type: 'item',
name: 'item 1',
data: {
section: {
name: 'name 1',
id: '1'
}
}
},
{
type: 'item',
name: 'item 2',
data: {
section: {
id: '1',
name: 'name 1'
}
}
},
{
type: 'section',
name: 'name 2',
id: '3'
},
{
type: 'item',
name: 'item 1',
data: {
section: {
id: '3',
name: 'name 2'
}
}
},
{
type: 'section',
name: 'name 3'
}, {
type: 'section',
name: 'name 4'
},
];
let section;
for (let item of array) {
console.log(item);
if (item.type === "section") {
section = item;
section.numberOfItems = 0;
} else if (item.type === "item" && section) {
section.numberOfItems++;
}
}
It steps through all elements, keeps a record when it finds a section and increments the count of the previously found section when it finds an item.

Categories