Efficiently loop through an object based on an array of ids - javascript

I am getting the users navigation "state" in a mobile navigation as an Array. For example:
['3124', '5312', '5232']
I need to use that state, to grab the Object that has the ID of '5232', 3 levels down in the object.
The Array length can differ, meaning it can return between 1 and 5 ids, so I don't always have to loop all the way down.
This is what the data for the navigation can look like, using the same IDs as I used in the example above, I would like my function to return the "evening" object with ID '5232':
[
{
id: "3124",
name: "women",
children: [
{
id: "5312",
name: "dresses",
children: [
{
id: "8399",
name: "wedding",
children: []
},
{
id: "5232",
name: "evening",
children: []
}
]
},
{
id: "3291",
name: "shoes",
children: []
}
]
},
{
id: "9482",
name: "men",
children: [
{
id: "8292",
name: "jackets",
children: []
},
{
id: "3829",
name: "hats",
children: []
}
]
}
]
I've been talking this through with a couple of colleagues and we can't really figure out a good way to do this efficiently. We cannot change the data, but we can probably change how the user state is saved, if that is wrong.
I could really use some input and ideas on how to solve this problem in a good way.

Simple function to find node by path in tree
function findNodeByPath(nodes, path) {
let node;
if (!path) return;
for (let id of path) {
if (!nodes) break;
for (let child of nodes) {
if (child.id === id) {
node = child;
nodes = node.children;
break;
}
}
}
return node;
}
let nodes = [
{
id: "3124",
name: "women",
children: [
{
id: "5312",
name: "dresses",
children: [
{
id: "8399",
name: "wedding",
children: []
},
{
id: "5232",
name: "evening",
children: []
}
]
},
{
id: "3291",
name: "shoes",
children: []
}
]
},
{
id: "9482",
name: "men",
children: [
{
id: "8292",
name: "jackets",
children: []
},
{
id: "3829",
name: "hats",
children: []
}
]
}
];
console.log(findNodeByPath(nodes, ['3124', '5312', '5232']));

You could iterate the arrays and take only the node of the given path id.
function getObject(tree, path) {
var temp = { children: tree };
return path.every(p => temp = temp.children.find(({ id }) => p === id))
? temp
: undefined;
}
var data = [{ id: "3124", name: "women", children: [{ id: "5312", name: "dresses", children: [{ id: "8399", name: "wedding", children: [] }, { id: "5232", name: "evening", children: [] }] }, { id: "3291", name: "shoes", children: [] }] }, { id: "9482", name: "men", children: [{ id: "8292", name: "jackets", children: [] }, { id: "3829", name: "hats", children: [] }] }],
path = ['3124', '5312', '5232'],
result = getObject(data, path);
console.log(result);

You can try implementing a basic nested for loop if focusing solely on efficiency. I chose the variable name breadcrumb since the concept seems similar to its role in site navigation.
function getState(breadcrumb, state) {
let states = state;
for (let id of breadcrumb) {
for (state of states) {
// found -- continue to next level
if (state.id === id) {
states = state.children;
break;
}
}
// not found
if (state.id !== id) {
return null;
}
}
return state;
}
let state = [{ id: "3124", name: "women", children: [{ id: "5312", name: "dresses", children: [{ id: "8399", name: "wedding", children: [] }, { id: "5232", name: "evening", children: [] }] }, { id: "3291", name: "shoes", children: [] }] }, { id: "9482", name: "men", children: [{ id: "8292", name: "jackets", children: [] }, { id: "3829", name: "hats", children: [] }] }];
let breadcrumb = ['3124', '5312', '5232'];
console.log(getState(breadcrumb, state));
However, if you care more about code maintainability, I recommend a more canonical approach:
function getState(breadcrumb, state) {
return breadcrumb.reduce((state, id) => {
return state !== null
? state.children.find(state => state.id === id)
: state;
}, { children: state });
}
let state = [{ id: "3124", name: "women", children: [{ id: "5312", name: "dresses", children: [{ id: "8399", name: "wedding", children: [] }, { id: "5232", name: "evening", children: [] }] }, { id: "3291", name: "shoes", children: [] }] }, { id: "9482", name: "men", children: [{ id: "8292", name: "jackets", children: [] }, { id: "3829", name: "hats", children: [] }] }];
let breadcrumb = ['3124', '5312', '5232'];
console.log(getState(breadcrumb, state));

try using recursive function
function findById(id) {
var founded = {};
function recurse(data){
for(var i = 0; i < data.length; i++) {
if (data[i].id === id) {
founded = data[i];
} else if (data[i].children && data[i].children.length) {
recurse(data[i].children);
}
}
}
recurse(catalog);
return founded;
};
some demo : Demo

Related

Find items and chech if it has the same id as in parent scope and if it has add isDisable to item

Hi everyone I need to get advice on how to realize such a function for searching and adding property if the children's scope has the same ids as the parent scope and add isDisable key.
The data which I got and I need to transform it with new property isDisable
const data = [
{
id: "MT1",
children: []
},
{
id: "MT2",
children: []
},
{
id: "MT4",
children: []
},
{
id: "1234",
children: [
{
id: "MT1",
children: []
},
{
id: "MT65",
children: []
},
]
},
{
id: "537465",
children: [{
id: "MT1",
children: []
},
{
id: "MT2",
children: [
{
id: "MT1",
children: []
},
{
id: "MT12",
children: []
}
]
},
]
}
]
This is some function for searching and adding a property to an item and result.
const someSearchFunction = (data) => {}
console.log(someSearchFunction(data))
const data = [
{
id: "MT1",
children: [],
isDisable: false
},
{
id: "MT2",
children: [],
isDisable: false
},
{
id: "MT4",
children: [],
isDisable: false
},
{
id: "MT12",
children: [],
isDisable: false
},
{
id: "1234",
children: [
{
id: "MT1",
children: [],
isDisable: true
},
{
id: "MT65",
children: [],
isDisable: false
},
]
},
{
id: "537465",
children: [
{
id: "MT1",
children: [],
isDisable: true
},
{
id: "42354322",
children: [
{
id: "MT1",
children: [],
isDisable: true
},
{
id: "MT12",
children: []
isDisable: false
}
]
},
]
}
]
Thanks!
We can do this by capturing the list of ids for the current level to pass on to the recursive call for our children.
Here is a version which does not mutate the input -- we're not barbarians! -- but returns a new tree with the isDisable property set appropiately.
const disableDupIds = (xs, ids = [], currLevel = xs .map (x => x .id)) => {
return xs .map (({id, children, ...rest}) => ({
id,
...rest,
isDisable: ids .includes (id),
children: disableDupIds (children, currLevel)
}))
}
const data = [{id: "MT1", children: []}, {id: "MT2", children: []}, {id: "MT4", children: []}, {id: "1234", children: [{id: "MT1", children: []}, {id: "MT65", children: []}]}, {id: "537465", children: [{id: "MT1", children: []}, {id: "MT2", children: [{id: "MT1", children: []}, {id: "MT12", children: []}]}]}]
console .log (disableDupIds (data))
.as-console-wrapper {max-height: 100% !important; top: 0}
We first capture the list of ids in our current level, then simply map over our elements, returning new versions with isDisable set true when our current id is in the list from the previous level, and recurring on our children, using our blacklist of current level ids.
Your expected output is not a valid array, better check it.
You can try this function:
const someSearchFunction = (data) => {
data.forEach(item => {
item.isDisable = false
item.children.some(child => {
if (data.some(parent => {
return parent.id === child.id
})) {
child.isDisable = true
} else {
child.isDisable = false
}
}
)
})
}

Remove/Add all childrenNode of a tree from an array when clicking on a parentNode

I have this nested object :
export interface RenderTree {
id: string;
name: string;
children?: readonly RenderTree[];
}
export const UserLoginRoleV2Tree: RenderTree = {
id: "root",
name: "APP",
children: [
{
id: "1",
name: UserLoginRoleV2.DASHBOARD
},
{
id: "2",
name: UserLoginRoleV2.REPORTING,
children: [
{
id: "2.1",
name: UserLoginRoleV2.REPORTS
},
{
id: "2.2",
name: UserLoginRoleV2.SALES_DETAILS
},
{
id: "2.3",
name: UserLoginRoleV2.LEADS_DETAILS
}
]
},
{
id: "3",
name: UserLoginRoleV2.SALES_DATA,
children: [
{
id: "3.1",
name: UserLoginRoleV2.SALES
},
{
id: "3.2",
name: UserLoginRoleV2.LEADS
},
{
id: "3.3",
name: UserLoginRoleV2.CALLS
},
{
id: "3.4",
name: UserLoginRoleV2.PHONE_CLOSING
}
]
},
{
id: "4",
name: UserLoginRoleV2.TRACKING,
children: [
{
id: "4.1",
name: UserLoginRoleV2.UNIVERSAL_SCRIPT
},
{
id: "4.2",
name: UserLoginRoleV2.SOURCE_LINKS
},
{
id: "4.3",
name: UserLoginRoleV2.URL_RULES
},
{
id: "4.4",
name: UserLoginRoleV2.PRODUCTS
},
{
id: "4.4",
name: UserLoginRoleV2.TAGS
}
]
},
{
id: "5",
name: UserLoginRoleV2.SETTINGS,
children: [
{
id: "5.1",
name: UserLoginRoleV2.PROFILE
},
{
id: "5.2",
name: UserLoginRoleV2.BILLING
},
{
id: "5.3",
name: UserLoginRoleV2.SUB_USERS
},
{
id: "5.4",
name: UserLoginRoleV2.CONNECTED_ACCOUNTS
},
{
id: "5.5",
name: UserLoginRoleV2.INTEGRATIONS
},
{
id: "5.6",
name: UserLoginRoleV2.TRACKING_SETTINGS
},
{
id: "5.7",
name: UserLoginRoleV2.TRACKING_DOMAINS
}
]
}
]
}
From this object, I'm able to build a Tree. Each node of the tree has a checkbox. To know if the checkbox is checked or not, I have a state array when I keep all nodes that are checked :
interface State {
roles: Array<string>;
}
<TreeItem key={nodes.id} nodeId={nodes.id} label={<Checkbox checked={this.state.roles.includes(nodes.name)} onClick={...}>
{Array.isArray(nodes.children)
? nodes.children.map((node) => this.renderTree(node))
: null}
</TreeItem>
I'm looking for a way to remove all children of a node from the array when clicking on it, knowing that a child can have children too.
I feel that we could make it with a recursive function, but I don't have success with that for now. Can you help me?
Okay I resolved it with Depth-first search
/**
* Get all children of a node.
* #param branch The node whose children you want to know
* #param callback Function to apply on each node.
*/
const breadthFirstSearch = (branch: TreeBranch): TreeBranch[] => {
let queue = new Array(branch);
let explored = new Array(branch);
let res = new Array();
let b: TreeBranch;
while (queue.length !== 0) {
b = queue.shift();
res.push(b);
b.children?.forEach(child => {
if (!explored.includes(child)) {
queue.push(child);
explored.push(child);
}
})
}
return res;
}

multilevel object traversing and print value based on key

Here is my object. There could be children inside of children and so on.
I Am trying to print only name for all of them.
Here is JSFiddle
var products = [
{
name: 'Allison',
children: [
{
name: 'John',
children: [
{
name: 'Scott',
children: [],
},
],
},
{
name: 'Sarah',
children: [],
},
]
},
{
name: 'Tony',
children: [
{
name: 'Lucy',
children: [],
}
]
}
This is what I have tried so far, how can I Print name of all children regardless of what level they are in the object??
for(var i = 0; i < products.length; i++)
{
console.log(products[i].name);
if(products[i].children.length > 0) {
console.log(products[i].children);
// Print only name of all children.
}
}
You can use recursive technical.
var products = [ { name: 'Allison', children: [{ name: 'John', children: [ { name: 'Scott', children: [],},],},{ name: 'Sarah', children: [],},]},{name: 'Tony',children: [{name: 'Lucy',children: [],}]}];
const printRecursively = (products) => {
for (const k of products)
{
console.log(k.name);
k.children.length > 0 && printRecursively(k.children); // Recurive here.
}
}
printRecursively(products);
Here is the implementation, please check
Also here
var products = [
{
name: 'Allison',
children: [
{
name: 'John',
children: [
{
name: 'Scott',
children: [],
},
],
},
{
name: 'Sarah',
children: [],
},
]
},
{
name: 'Tony',
children: [
{
name: 'Lucy',
children: [],
}
]
}
];
function printChildrenNames(children) {
for(var i = 0; i < children.length; i++) {
console.log(children[i].name);
if(children[i].children.length > 0) {
printChildrenNames(children[i].children);
}
}
}
printChildrenNames(products)
Here is an iterative solution using object-scan. For your use case a simple recursive solution might be the best choice, however this solution is very clean and easily adjustable when requirements change.
// const objectScan = require('object-scan');
const data = [{ name: 'Allison', children: [{ name: 'John', children: [{ name: 'Scott', children: [] }] }, { name: 'Sarah', children: [] }] }, { name: 'Tony', children: [{ name: 'Lucy', children: [] }] }];
console.log(objectScan(['**.name'], { rtn: 'value', reverse: false })(data));
// => [ 'Allison', 'John', 'Scott', 'Sarah', 'Tony', 'Lucy' ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#14.0.0"></script>
Disclaimer: I'm the author of object-scan

Remove items from nested array of objects based on a condition

In my application, I have data returned from the server like below. It has very deep nesting:
var data = [{
name: "root",
children: [{
name: "Parent1",
children: [{
name: "Parent1-child1",
children: [{
name: "Parent1-child1-grandchild1",
children: [{
name: "Parent1-child1-grandchild1-last",
children:[]
}]
},
{
name: "Parent1-child1-grandchild2",
children: []
},
{
name: "Parent1-child1-grandchild3",
children: []
}
]
},
{
name: "Paren1-child2",
children: [{
name: "Parent1-chil2-grandchild1",
children: []
},
{
name: "Parent1-child2-grandchild2",
children: [{
name: "Parent1-child2-grandchild2-last",
children: []
}]
},
{
name: "Parent1-child2-grandchild3",
children: []
}
]
},
{
name: "Parent1-child3",
children: []
}
]
},
{
name: "Parent2",
children: [{
name: "Parent2-child1",
children: []
},
{
name: "Parent2-child2",
children: [{
name: "Parent2-child2-grandchild1",
children: []
},
{
name: "Parent2-child2-grandchild2",
children: [{
name: "Parent2-child2-grandchild2-last",
children: []
}]
}
]
}
]
},
{
name: "Parent3",
children: []
}
]
}];
The requirement is to loop through all the objects (deep nested level also) and remove the object if the children property has a value of an empty array. So the output should be like below
var data = [{
name: "root",
children: [{
name: "Parent1",
children: [{
name: "Parent1-child1",
children: [{
name: "Parent1-child1-grandchild1",
children: []
},
]
},
{
name: "Paren1-child2",
children: [
{
name: "Parent1-child2-grandchild2",
children: []
},
]
},
]
},
{
name: "Parent2",
children: [
{
name: "Parent2-child2",
children: [
{
name: "Parent2-child2-grandchild2",
children: []
}
]
}
]
}
]
}];
I have tried the following code, but it doesn't work as expected. Please let me know how to achieve the expected result.
function checkChildrens(arr) {
arr.forEach((ele,i) => {
if(ele.hasOwnProperty('children')) {
checkChildrens(ele['children'])
} else {
arr.splice(i,1)
}
})
}
checkChildrens(data);
I have tried with the filter method also in that case. It is not working correctly.
arr.filter((ele,i)=>{
if(ele.hasOwnProperty('children') && ele.children.length !== 0 ){
removeEmpty(ele.children)
}else{
return false;
}
return true;
})
You could rebuild new objects by checking the children array length.
function filter(array) {
return array.reduce((r, o) => {
if (o.children && o.children.length) {
r.push(Object.assign({}, o, { children: filter(o.children) }));
}
return r;
}, []);
}
var data = [{ name: "root", children: [{ name: "Parent1", children: [{ name: "Parent1-child1", children: [{ name: "Parent1-child1-grandchild1", children: [{ name: "Parent1-child1-grandchild1-last", children: [] }] }, { name: "Parent1-child1-grandchild2", children: [] }, { name: "Parent1-child1-grandchild3", children: [] }] }, { name: "Paren1-child2", children: [{ name: "Parent1-chil2-grandchild1", children: [] }, { name: "Parent1-child2-grandchild2", children: [{ name: "Parent1-child2-grandchild2-last", children: [] }] }, { name: "Parent1-child2-grandchild3", children: [] }] }, { name: "Parent1-child3", children: [] }] }, { name: "Parent2", children: [{ name: "Parent2-child1", children: [] }, { name: "Parent2-child2", children: [{ name: "Parent2-child2-grandchild1", children: [] }, { name: "Parent2-child2-grandchild2", children: [{ name: "Parent2-child2-grandchild2-last", children: [] }] }] }] }, { name: "Parent3", children: [] }] }],
result = filter(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Approach for removing all nested empty children (except the last one. This has an empty object, but no children property).
function filter(array) {
return array.reduce((r, o) => {
if (o.children) {
var children = filter(o.children);
if (children.length) r.push(Object.assign({}, o, { children }));
} else {
r.push(o);
}
return r;
}, []);
}
var data = [{ name: "root", children: [{ name: "Parent1", children: [{ name: "Parent1-child1", children: [{ name: "Parent1-child1-grandchild1", children: [{ name: "Parent1-child1-grandchild1-last", children: [] }] }, { name: "Parent1-child1-grandchild2", children: [] }, { name: "Parent1-child1-grandchild3", children: [] }] }, { name: "Paren1-child2", children: [{ name: "Parent1-chil2-grandchild1", children: [] }, { name: "Parent1-child2-grandchild2", children: [{ name: "Parent1-child2-grandchild2-last", children: [] }] }, { name: "Parent1-child2-grandchild3", children: [] }] }, { name: "Parent1-child3", children: [] }] }, { name: "Parent2", children: [{ name: "Parent2-child1", children: [] }, { name: "Parent2-child2", children: [{ name: "Parent2-child2-grandchild1", children: [] }, { name: "Parent2-child2-grandchild2", children: [{ name: "Parent2-child2-grandchild2-last", children: [] }] }] }] }, { name: "Parent3", children: [{}] }] }],
result = filter(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var data = [{
name: "root",
children: [{
name: "Parent1",
children: [{
name: "Parent1-child1",
children: [{
name: "Parent1-child1-grandchild1",
children: [{
name: "Parent1-child1-grandchild1-last",
children: []
}]
},
{
name: "Parent1-child1-grandchild2",
children: []
},
{
name: "Parent1-child1-grandchild3",
children: []
}
]
},
{
name: "Paren1-child2",
children: [{
name: "Parent1-chil2-grandchild1",
children: []
},
{
name: "Parent1-child2-grandchild2",
children: [{
name: "Parent1-child2-grandchild2-last",
children: []
}]
},
{
name: "Parent1-child2-grandchild3",
children: []
}
]
},
{
name: "Parent1-child3",
children: []
}
]
},
{
name: "Parent2",
children: [{
name: "Parent2-child1",
children: []
},
{
name: "Parent2-child2",
children: [{
name: "Parent2-child2-grandchild1",
children: []
},
{
name: "Parent2-child2-grandchild2",
children: [{
name: "Parent2-child2-grandchild2-last",
children: []
}]
}
]
}
]
},
{
name: "Parent3",
children: []
}
]
}];
function checkChildrens(arr) {
let res = []
arr.forEach(v => {
if (v.children && v.children.length) {
res = res.concat({
name: v.name,
children: checkChildrens(v.children)
})
}
})
return res
}
console.log(checkChildrens(data));

Find node by id in json tree

I want to add the children array to node where id = 52126f7d (or another). How do I do it?
var children = [
{ name: 'great-granchild3',
id: '2a12a10h'
},
{ name: 'great-granchild4',
id: 'bpme7qw0'
}
]
// json tree
var objects = {
name: 'all objects',
id:"2e6ca1c3",
children: [
{
name: 'child',
id: "6c03cfbe",
children: [
{ name: 'grandchild1',
id: "2790f59c"
},
{ name: 'grandchild2',
id: "52126f7d"
},
{ name: 'grandchild3',
id: "b402f14b"
},
{
name: 'grandchild4',
id: "6c03cff0",
children: [
{ name: 'great-grandchild1',
id: "ce90ffa6"
},
{ name: 'great-grandchild2',
id: "52f95f28"
}
]
}
]
},
{
name: 'child2',
id: "7693b310",
children: [
{ name: 'grandchild5',
id: "def86ecc"
},
{ name: 'grandchild6',
id: "6224a8f8"
}
]
}
]
}
to end up with
var objects = {
name: 'all objects',
id:"2e6ca1c3",
children: [
{
name: 'child',
id: "6c03cfbe",
children: [
{ name: 'grandchild1',
id: "2790f59c"
},
{ name: 'grandchild2',
id: "52126f7d",
children = [
{ name: 'great-granchild3',
id: '2a12a10h'
},
{ name: 'great-granchild4',
id: 'bpme7qw0'
}
]
},
{ name: 'grandchild3',
id: "b402f14b"
},
{
name: 'grandchild4',
id: "6c03cff0",
children: [
{ name: 'great-grandchild1',
id: "ce90ffa6"
},
{ name: 'great-grandchild2',
id: "52f95f28"
}
]
}
]
},
{
name: 'child2',
id: "7693b310",
children: [
{ name: 'grandchild5',
id: "def86ecc"
},
{ name: 'grandchild6',
id: "6224a8f8"
}
]
}
]
}
by finding the proper node first.
function getNodeById(id, node){
var reduce = [].reduce;
function runner(result, node){
if(result || !node) return result;
return node.id === id && node || //is this the proper node?
runner(null, node.children) || //process this nodes children
reduce.call(Object(node), runner, result); //maybe this is some ArrayLike Structure
}
return runner(null, node);
}
var target = getNodeById("52126f7d", objects);
target.children = children;
How about:
objects.children[0].children[1].children = children;

Categories