Flatten a deeply nested array with objects and arrays - javascript

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

Related

Create an array of objects with values from another object

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

How to change all occurrences of an object key in an array of objects

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

How can I loop in an multi-dimensional array to get the capture the the last node using spread operator

Consider this example. I am trying to move to first element of 2-D ARRAY which contains two array items and use spread operator until I get text inside the inner most array.
let arr = [
[ {
type: 'paragraph',
children: [{ text: 'First line.' }],
},
{
type: 'paragraph',
children: [{ text: 'Second.' }],
}
],[
{
type: 'paragraph',
children: [{ text: 'Third.' }],
},
{
type: 'paragraph',
children: [{ text: 'Fourth.' }],
}
]
]
//const newArr = [...arr[0]] // more logic
//result to ['first line','Second.']
JS Fiddle to test the array
I dont think the spread operator will be of any help here, but consider this:
let arr = [
[ {
type: 'paragraph',
children: [{ text: 'First line.' }],
},
{
type: 'paragraph',
children: [{ text: 'Second.' }],
}
],[
{
type: 'paragraph',
children: [{ text: 'Third.' }],
},
{
type: 'paragraph',
children: [{ text: 'Fourth.' }],
}
]
]
let result1 = [];
arr.flat().forEach(element => result1.push(element.children[0].text));
console.log("Result1:", result1);
// or just group the children from arr[0] together:
let result2 = [];
arr[0].forEach(element => result2.push(element.children[0]));
console.log("Result2:", result2);

Javascript - remove object out of nested Object array

I have a nested Object array and I would like to remove an item out of this nested array, but for some reason this does not seem to work with my approach:
Object
export const completeNavigationItemsV2Response = [
{
id: 'Erlebniskategorien',
title: 'Erlebniskategorien',
uri: '/on/demandware.store/Sites-JSShop-Site/default/SearchJS-Show',
children: [
{
id: 'fliegen-fallen',
title: 'Fallen & Springen',
uri: '/fliegen-fallen/fallen-springen,default,sc.html',
children: [
{
id: 'fallen-springen',
title: 'Fallen & Springen',
uri: '/fliegen-fallen/fallen-springen,default,sc.html',
children: [],
}
],
},
{
id: 'Weit-Weg',
title: 'Reisen & Kurzurlaub',
uri: '/reisen/Weit-Weg,default,sc.html',
children: [
{
id: 'staedtereisen',
title: 'Städtereisen',
uri: '/reisen/staedtereisen,default,sc.html',
children: [],
}
],
},
{
id: 'motorpower',
title: 'Motorpower',
uri: '/geschenke-maenner/motorpower,default,sc.html',
children: [
{
id: 'rennwagen',
title: 'Rennwagen',
uri: '/motorpower/rennwagen,default,sc.html',
children: [],
}
],
},
{
id: '10',
title: 'Erlebnisse mit Stars',
uri: '/erlebnisse-mit-stars/l/10',
children: [
{ // <== remove this object with id === 'glossar'
id: 'glossar',
title: 'Glossar',
uri: '/erlebnisstandorte/glossar,default,pg.html',
children: [],
},
],
},
],
}
];
Does someone of you would now a handy es6 way how to remove that subObject from the whole object in a somewhat dynamic way like with the .map() or .filter() function?
If you want it for any level in your object, you could do it with a recursive function like so:
// Object is the same, just minified
const completeNavigationItemsV2Response=[{id:"Erlebniskategorien",title:"Erlebniskategorien",uri:"/on/demandware.store/Sites-JSShop-Site/default/SearchJS-Show",children:[{id:"fliegen-fallen",title:"Fallen & Springen",uri:"/fliegen-fallen/fallen-springen,default,sc.html",children:[{id:"fallen-springen",title:"Fallen & Springen",uri:"/fliegen-fallen/fallen-springen,default,sc.html",children:[]}]},{id:"Weit-Weg",title:"Reisen & Kurzurlaub",uri:"/reisen/Weit-Weg,default,sc.html",children:[{id:"staedtereisen",title:"Städtereisen",uri:"/reisen/staedtereisen,default,sc.html",children:[]}]},{id:"motorpower",title:"Motorpower",uri:"/geschenke-maenner/motorpower,default,sc.html",children:[{id:"rennwagen",title:"Rennwagen",uri:"/motorpower/rennwagen,default,sc.html",children:[]}]},{id:"10",title:"Erlebnisse mit Stars",uri:"/erlebnisse-mit-stars/l/10",children:[{id:"glossar",title:"Glossar",uri:"/erlebnisstandorte/glossar,default,pg.html",children:[]}]}]}];
const removeItemWithId = (array, id) => {
return array
.filter(obj => obj.id !== id) // filter out if the id matches
.map(obj => ({ // Do the same for children (if they exist)
...obj,
children: obj.children !== undefined
? removeItemWithId(obj.children, id)
: undefined
}));
};
console.log(removeItemWithId(completeNavigationItemsV2Response, 'glossar'));
Although newer than ES6, if you can support .flatMap(), you can do this recursively by calling .flatMap() on your initial array and then calling it on your children array. If you reach the element which you want to remove, you can return an empty array [], which will remove the object when concatenated into the resulting array.
const arr = [{ id: 'Erlebniskategorien', title: 'Erlebniskategorien', uri: '/on/demandware.store/Sites-JSShop-Site/default/SearchJS-Show', children: [{ id: 'fliegen-fallen', title: 'Fallen & Springen', uri: '/fliegen-fallen/fallen-springen,default,sc.html', children: [{ id: 'fallen-springen', title: 'Fallen & Springen', uri: '/fliegen-fallen/fallen-springen,default,sc.html', children: [], }], }, { id: 'Weit-Weg', title: 'Reisen & Kurzurlaub', uri: '/reisen/Weit-Weg,default,sc.html', children: [{ id: 'staedtereisen', title: 'Städtereisen', uri: '/reisen/staedtereisen,default,sc.html', children: [], }], }, { id: 'motorpower', title: 'Motorpower', uri: '/geschenke-maenner/motorpower,default,sc.html', children: [{ id: 'rennwagen', title: 'Rennwagen', uri: '/motorpower/rennwagen,default,sc.html', children: [], }], }, { id: '10', title: 'Erlebnisse mit Stars', uri: '/erlebnisse-mit-stars/l/10', children: [{ id: 'glossar', title: 'Glossar', uri: '/erlebnisstandorte/glossar,default,pg.html', children: [], }, ], }, ], }];
const removeId = "glossar";
const res = arr.flatMap(function fn(o) {
return o.id !== removeId ? ({...o, children: o.children.flatMap(fn)}) : [];
});
console.log(res);

filter nested object array in javascript

I want to sort the above given array so that the output array will be as given in output section. I have tried some code which is given below. I am using javascript for sorting. In angular I am using this To display menu according to user role.
I have googled a lot but not getting solution
this.items = [
{
label: 'Home', routerLink: ['Home']
},
{
label: 'menu1',
items: [
{
label: 'submenu1',
routerLink: '/submenu1'
},
{
label: 'submenu2'
, routerLink: '/submenu2'
},
{
label: 'submenu3',
routerLink: ['/submenu3']
}
]
},
{
label: 'menu2',
items: [
{
label: 'submenu5',
routerLink: ['/submenu5']
},
]
},
{
label: 'menu3',
items: [
{
label: 'submenu6',
routerLink: ['/submenu6'],
}
]
},
];
output:
this.items = [
{
label: 'Home', routerLink: ['Home']
},
{
label: 'menu1',
items: [
{
label: 'submenu1',
routerLink: '/submenu1'
}
]
},
{
label: 'menu3',
items: [
{
label: 'submenu6',
routerLink: ['/submenu6'],
}
]
},
];
code for sorting:
let filterArr = this.filteredArray
.filter(x => x.label == "Home" && x.label == "menu3")
.map(y => y.items.filter(z => z.label == 'submenu6'));
You could move the wanted menu and submenu labels into arrays and filter the objects by creating new object with filtered menus.
This approach does not mutate the data.
It uses Array#flatMap for the outer array and Array#filter for getting the wanted parts of the nested array.
If the nested array does not have any item, then take the original object.
var items = [{ label: 'Home', routerLink: ['Home'] }, { label: 'menu1', items: [{ label: 'submenu1', routerLink: '/submenu1' }, { label: 'submenu2', routerLink: '/submenu2' }, { label: 'submenu3', routerLink: ['/submenu3'] }] }, { label: 'menu2', items: [{ label: 'submenu5', routerLink: ['/submenu5'] }] }, { label: 'menu3', items: [{ label: 'submenu6', routerLink: ['/submenu6'] }] }],
menu = ['Home', 'menu1', 'menu3'],
submenu = ['submenu1', 'submenu6'],
result = items.flatMap(o => {
if (!menu.includes(o.label)) return [];
var items = (o.items || []).filter(({ label }) => submenu.includes(label));
return items.length ? { ...o, items } : o;
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources