0

I'm trying to find a specific Object in a nested Object by id and wrote this function, which works like a charm:

const findNestedObjById = (tree, myFunction, id) => {
 if(tree.attributes.node_id === id){
 myFunction(tree)
 } else{
 if(tree.children){
 tree.children.forEach(child => {
 findNestedObjById(child, myFunction, id) 
 });
 } 
 }
};
const doThat = (tree) => {
 console.log("Got it: " + tree.name)
}
findNestedObjById(myObj, doThat, "0.1.2.1");

But i want to be able to get the "path" of the object (e.g. myObj.children[0].children[2]) (The children property of my object is an array) So I wanted to rewrite the function using a fori loop instead of a foreach, so that I could later add the index of the array (saved in i of the fori loop at the time) to a path string.

So I wanted to start with this function:

const findWithFori = (tree, myFunction, id) => {
 if(tree.attributes.node_id === id){
 myFunction(tree)
 } else{
 if(tree.children){
 for (let i = 0; i < tree.length; i++) {
 const child = tree.children[i];
 findNestedObjById(child, myFunction, id) 
 }
 } 
 }
};

But it doenst work, it's able to locate the object by id, if the inital myObj already has the right id, but it doesn't find nested objects, like the first function does and I don't understand why.

If it helps answerign the question, myObj looks like this btw.:

const myObj = {
 name: "Mein zweiter Baum",
 attributes: {
 node_id: "0"
 },
 children: [
 {
 name: "Lorem",
 attributes: {
 node_id: "0.1",
 done: true
 },
 children: [
 {
 name: "Ipsum",
 attributes: {
 node_id: "0.1.1",
 done: true
 },
 children: [
 {
 name: "Dolor",
 attributes: {
 node_id: "0.1.1.1",
 done: false
 }
 }
 ]
 },
 {
 name: "Sit",
 attributes: {
 node_id: "0.1.2",
 done: false
 },
 children: [
 {
 name: "Anet",
 attributes: {
 node_id: "0.1.2.1"
 }
 }
 ]
 }
 ]
 }
 ]
};
asked Mar 5, 2022 at 14:10

2 Answers 2

2

You could return the indices.

If an item is found return an empty array, or undefined. Inside of some get the result of children and if not undefined add the actual index in front of the array.

const
 findNestedObjById = (tree, id, callback) => {
 if (tree.attributes.node_id === id) {
 callback(tree);
 return [];
 }
 if (tree.children) {
 let path;
 tree.children.some((child, index) => {
 path = findNestedObjById(child, id, callback);
 if (path) {
 path.unshift(index);
 return true;
 }
 });
 return path;
 }
 },
 doThat = tree => {
 console.log("Got it: " + tree.name);
 },
 data = { name: "Mein zweiter Baum", attributes: { node_id: "0" }, children: [{ name: "Lorem", attributes: { node_id: "0.1", done: true }, children: [{ name: "Ipsum", attributes: { node_id: "0.1.1", done: true }, children: [{ name: "Dolor", attributes: { node_id: "0.1.1.1", done: false } }] }, { name: "Sit", attributes: { node_id: "0.1.2", done: false }, children: [{ name: "Anet", attributes: { node_id: "0.1.2.1" } }] }] }] }
console.log(findNestedObjById(data, "0.1.2.1", doThat)); // [0, 1, 0]
.as-console-wrapper { max-height: 100% !important; top: 0; }

answered Mar 5, 2022 at 14:29
Sign up to request clarification or add additional context in comments.

Comments

1

I would do this by building atop some reusable functions. We can write a function that visits a node and then recursively visits all its children's nodes. To use this for a find, however, we want to be able to stop once its found, so a generator function would make sense here. We can extend a basic version of this 1 to allow each stop to include not only the values, but also their paths.

Then we can layer on a generic find-path-by-predicate function, testing each node it generates until one matches the predicate.

Finally we can easily write a function using this to search by node_id. It might look like this:

function * visit (value, path = []) {
 yield {value, path}
 for (let i = 0; i < (value .children || []) .length; i ++) {
 yield * visit (value .children [i], path .concat (i))
 }
}
const findDeepPath = (fn) => (obj) => {
 for (let o of visit (obj)) {
 if (fn (o .value)) {return o .path}
 }
}
const findPathByNodeId = (id) => 
 findDeepPath (({attributes: {node_id}}) => node_id === id)
const myObj = {name: "Mein zweiter Baum", attributes: {node_id: "0"}, children: [{name: "Lorem", attributes: {node_id: "0.1", done: true}, children: [{name: "Ipsum", attributes: {node_id: "0.1.1", done: true}, children: [{name: "Dolor", attributes: {node_id: "0.1.1.1", done: false}}]}, {name: "Sit", attributes: {node_id: "0.1.2", done: false}, children: [{name: "Anet", attributes: {node_id: "0.1.2.1"}}]}]}]}
console .log (findPathByNodeId ('0.1.2.1') (myObj)) //=> [0, 1, 0]

If we want to return the node and the path, it's simply a matter of replacing

 if (fn (o .value)) {return o .path}

with

 if (fn (o .value)) {return o}

and we would get back something like:

{
 value: {attributes: {node_id: "0.1.2.1"}, name: "Anet"},
 path: [0, 1, 0],
}

1 A basic version for nodes without their paths might look like this:

function * visit (obj) {
 yield obj
 for (let child of (obj .children || [])) {
 yield * visit (child)
 }
}

and we might write a generic search for values matching a predicate with

const findDeep = (fn) => (obj) => {
 for (let o of visit (obj)) {
 if (fn (o)) {return o}
 }
}

Layering in the path handling adds some complexity, but not a great deal.

answered Mar 6, 2022 at 22:14

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.