The problem what you have, is the bubbling of the find. If the id is found inside the nested structure, the callback tries to returns the element, which is interpreted as true, the value for the find.

The find method executes the callback function once for each element present in the array until it finds one where callback returns a true value. [MDN]

Instead of find, I would suggest to use a recursive style for the search with a short circuit if found.

var data = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4, children: [{ id: 6 }, { id: 7, children: [{ id: 8 }, { id: 9 }] }] }, { id: 5 }];

function findById(data, id) {
    function iter(a) {
        if (a.id === id) {
            result = a;
            return true;
        }
        return Array.isArray(a.children) && a.children.some(iter);
    }

    var result;
    data.some(iter);
    return result
}

console.log(findById(data, 8));

Answer from Nina Scholz on Stack Overflow
Top answer
1 of 13
35

The problem what you have, is the bubbling of the find. If the id is found inside the nested structure, the callback tries to returns the element, which is interpreted as true, the value for the find.

The find method executes the callback function once for each element present in the array until it finds one where callback returns a true value. [MDN]

Instead of find, I would suggest to use a recursive style for the search with a short circuit if found.

var data = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4, children: [{ id: 6 }, { id: 7, children: [{ id: 8 }, { id: 9 }] }] }, { id: 5 }];

function findById(data, id) {
    function iter(a) {
        if (a.id === id) {
            result = a;
            return true;
        }
        return Array.isArray(a.children) && a.children.some(iter);
    }

    var result;
    data.some(iter);
    return result
}

console.log(findById(data, 8));

2 of 13
10

Let's consider the implementation based on recursive calls:

function findById(tree, nodeId) {
  for (let node of tree) {
    if (node.id === nodeId) return node

    if (node.children) {
      let desiredNode = findById(node.children, nodeId)
      if (desiredNode) return desiredNode
    }
  }
  return false
}

Usage

var data = [
  { id: 1 }, { id: 2 }, { id: 3 },
  { id: 4, children: [
      { id: 6 },
      { id: 7,
        children: [
          { id: 8 }, 
          { id: 9 }
        ]}]},
  { id: 5 }
]

findById(data,  7 ) // {id: 7, children: [{id: 8}, {id: 9}]}
findById(data,  5 ) // {id: 5}
findById(data,  9 ) // {id: 9}
findById(data,  11) // false

To simplify the picture, imagine that:

  • you are the monkey sitting on the top of a palm tree;
  • and searching for a ripe banana, going down the tree
  • you are in the end and searches aren't satisfied you;
  • come back to the top of the tree and start again from the next branch;
  • if you tried all bananas on the tree and no one is satisfied you, you just assert that ripe bananas don't grow on this this palm;
  • but if the banana was found you come back to the top and get pleasure of eating it.

Now let's try apply it to our recursive algorithm:

  1. Start iteration from the top nodes (from the top of the tree);
  2. Return the node if it was found in the iteration (if a banana is ripe);
  3. Go deep until item is found or there will be nothing to deep. Hold the result of searches to the variable (hold the result of searches whether it is banana or just nothing and come back to the top);
  4. Return the searches result variable if it contains the desired node (eat the banana if it is your find, otherwise just remember not to come back down by this branch);
  5. Keep iteration if node wasn't found (if banana wasn't found keep testing other branches);
  6. Return false if after all iterations the desired node wasn't found (assert that ripe bananas doesn't grow on this tree).

Keep learning recursion it seems not easy at the first time, but this technique allows you to solve daily issues in elegant way.

🌐
GoLinuxCloud
golinuxcloud.com › home › javascript › recursive search in array of objects javascript? [solved]
Recursive search in array of objects JavaScript? [SOLVED] | GoLinuxCloud
January 17, 2023 - This function will check all elements of the array and recursively check all children arrays until it finds the first object that matches the specified value.
Discussions

javascript - Find an element in an array recursively
0 How to write a recursive function that searches through an array to find the index of a target element (JS) More on stackoverflow.com
🌐 stackoverflow.com
Recursive find function in JavaScript / jQuery - Code Review Stack Exchange
I wrote the following code as a way of plucking data from a n-depth JavaScript object. It works a charm and even appends the parents of the the found item. I was just wondering if you guys had any... More on codereview.stackexchange.com
🌐 codereview.stackexchange.com
April 13, 2017
Search recursively for value in object by property name
Find centralized, trusted content and collaborate around the technologies you use most. Learn more about Collectives ... Bring the best of human thought and AI automation together at your work. Explore Stack Internal ... I'm building an utility function that should search for a property name and return its value once it is found. It should do this recursively... More on stackoverflow.com
🌐 stackoverflow.com
November 15, 2016
arrays - Algorithm to find a "recursive" item in a Javascript data structure - Stack Overflow
I think it's about finding property values that have been re-used in a way that violates some contract. ... I think you mean "recurring" or "duplicate" not recursive. Recursive would be if you had a value for one of the members of the object set to the parent object. ... @Pointy Yes, JSON.stri... More on stackoverflow.com
🌐 stackoverflow.com
Top answer
1 of 8
63

When searching recursively, you have to pass the result back by returning it. You're not returning the result of findNode(id, currentChild), though.

Copyfunction findNode(id, currentNode) {
    var i,
        currentChild,
        result;

    if (id == currentNode.id) {
        return currentNode;
    } else {

        // Use a for loop instead of forEach to avoid nested functions
        // Otherwise "return" will not work properly
        for (i = 0; i < currentNode.children.length; i += 1) {
            currentChild = currentNode.children[i];

            // Search in the current child
            result = findNode(id, currentChild);

            // Return the result if the node has been found
            if (result !== false) {
                return result;
            }
        }

        // The node has not been found and we have no more options
        return false;
    }
}
2 of 8
5
Copyfunction findNode(id, currentNode) {

    if (id == currentNode.id) {
        return currentNode;
    } else {
        var result;
        currentNode.children.forEach(function(node){
            if(node.id == id){
                result = node;
                return;
            }
        });
        return (result ? result : "No Node Found");
    }
}
console.log(findNode("10", node));

This method will return the node if it present in the node list. But this will loop through all the child of a node since we can't successfully break the forEach flow. A better implementation would look like below.

Copyfunction findNode(id, currentNode) {

    if (id == currentNode.id) {
        return currentNode;
    } else {
        for(var index in currentNode.children){
            var node = currentNode.children[index];
            if(node.id == id)
                return node;
            findNode(id, node);
        }
        return "No Node Present";
    }
}
console.log(findNode("1", node));
🌐
CheatCode
cheatcode.co › blog › how-to-recursively-traverse-an-object-with-javascript
How to Recursively Traverse an Object with JavaScript | CheatCode
How to write a function that looks for a specific key/value pair on an object and call that function recursively to traverse objects of an arbitrary depth. ... This post is sponsored by Mod, the CSS framework for SaaS apps. This post is more than two years old—some or all of the information here may be out of date and some of the content may be broken. Use caution. For this tutorial, we're going to create a simple, single file Node.js project.
🌐
TypeOfNaN
typeofnan.dev › how-to-find-an-object-deeply-and-do-something-with-the-result-in-javascript
How to Find an Object Deeply and Do Something with the Result in JavaScript | TypeOfNaN
March 13, 2021 - function deepFindCallback(obj, matcher, cb) { // Call the matcher function if (matcher(obj)) { // If match, call the callback cb(obj); } // If not match, recursively call deepFindCallback for (let key in obj) { if (typeof obj[key] === 'object') { deepFindCallback(obj[key], matcher, cb); } } } Let’s try it out! Our example states that we want to find the object associated with id = 7, so we can use the following matcher function:
Top answer
1 of 2
38

You should replace

  getSubMenuItem(subMenuItems[i].items, id);

with

  var found = getSubMenuItem(subMenuItems[i].items, id);
  if (found) return found;

in order to return the element when it is found.

And be careful with the name of the properties, javascript is case sensitive, so you must also replace

  if (subMenuItems[i].Id == id) {

with

  if (subMenuItems[i].id == id) {

Demonstration


Final (cleaned) code :

var getSubMenuItem = function (subMenuItems, id) {
    if (subMenuItems) {
        for (var i = 0; i < subMenuItems.length; i++) {
            if (subMenuItems[i].id == id) {
                return subMenuItems[i];
            }
            var found = getSubMenuItem(subMenuItems[i].items, id);
            if (found) return found;
        }
    }
};
2 of 2
3

I know its late but here is a more generic approach

Array.prototype.findRecursive = function(predicate, childrenPropertyName){
    if(!childrenPropertyName){
        throw "findRecursive requires parameter `childrenPropertyName`";
    }
    let array = [];
    array = this;
    let initialFind =  array.find(predicate);
    let elementsWithChildren  = array.filter(x=>x[childrenPropertyName]);
    if(initialFind){
        return initialFind;
    }else if(elementsWithChildren.length){
        let childElements = [];
        elementsWithChildren.forEach(x=>{
            childElements.push(...x[childrenPropertyName]);
        });
        return childElements.findRecursive(predicate, childrenPropertyName);
    }else{
        return undefined;
    }
}

to use it:

var array = [<lets say an array of students who has their own students>];
var joe = array.findRecursive(x=>x.Name=="Joe", "students");

and if you want filter instead of find

Array.prototype.filterRecursive = function(predicate, childProperty){
    let filterResults = [];
    let filterAndPushResults = (arrayToFilter)=>{
        let elementsWithChildren  = arrayToFilter.filter(x=>x[childProperty]);
        let filtered = arrayToFilter.filter(predicate);
        filterResults.push(...filtered);
        if(elementsWithChildren.length){
            let childElements = [];
            elementsWithChildren.forEach(x=>{
                childElements.push(...x[childProperty]);
            });
            filterAndPushResults(childElements);
        }
    };
    filterAndPushResults(this);
    return filterResults;
}
🌐
GitHub
gist.github.com › shakhal › 3cf5402fc61484d58c8d
Find values in JSON by key, recursively · GitHub
Clone this repository at &lt;script src=&quot;https://gist.github.com/shakhal/3cf5402fc61484d58c8d.js&quot;&gt;&lt;/script&gt; Save shakhal/3cf5402fc61484d58c8d to your computer and use it in GitHub Desktop. ... This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters ... Thank you so much! ... findValuesHelper(obj, key) { let list = [ ]; if (!obj) return list; if (obj instanceof Array) { for (var
Find elsewhere
🌐
DEV Community
dev.to › a_b_102931 › searching-through-a-nested-object-using-recursion-regular-expressions-and-sets-bm7
Searching Through a Nested Object Using Recursion, Regular Expressions, and Sets - DEV Community
March 11, 2020 - The tricky thing with nested objects is that some values--but not all--are objects themselves. That means we need to use recursion to iterate through those values, and do so until we the value is a string. Here, we can use typeof to check if the value at each key is an object.
Top answer
1 of 1
6

Overal

I think the code looks too busy just to find a key/value match in an object tree-structure, it took a while to figure out which part(s) bothered me.

Doing it twice

You are doing the matching twice, once with (data[key] === match) and once with (v[key] === match), it would be cleaner to leave the second check to recursion.

Function in a function

I am assuming you use a function in a function to keep track of depth and parents, which isn't worth it. You can do this in a single function with an optional parameter.

Naming

  • match -> matchValue because match sounds like a boolean to me
  • childKey - childrenKey because the key really points to an array
  • result.parents -> avoid this, what if the object already has a property called parents ?

Counter proposal

If you think about it, result can be considered the parent of key, so I would build a function that returns all parents in an array, if you wish you can then have recursiveFind call that.

function findParents( data, key, matchValue, childrenKey, parents )
{
  var i , children , childrenCount;
  /* Prevent the need for an inner function by re-passing parents */
  parents = parents || [];
  parents.push( data )
  /* Return immediately if we find a match */
  if( data[key] === matchValue )
    return parents.reverse(); 
  /* Are there children to inspect ? */
  if( !data[childrenKey] || !data[childrenKey].length )
    return false;
  children = data[childrenKey];
  childrenCount = children.length;
  /* See if any children have it ? */
  for( i = 0 ; i < childrenCount ; i++ )
  {
    if( parents = findParents( children[i] , key , matchValue , childrenKey , parents.slice() ) )
      return parents;
  }
  /* Fail.. */
  return false;
}

Then recursiveFind becomes

function recursiveFind(data, key, childrenKey, match )
{
  var parents = findParents( data , key, match ),
      result = parents.shift()
  result.parents = parents;
  return result;
}
Top answer
1 of 13
47

You could use Object.keys and iterate with Array#some.

function findVal(object, key) {
    var value;
    Object.keys(object).some(function(k) {
        if (k === key) {
            value = object[k];
            return true;
        }
        if (object[k] && typeof object[k] === 'object') {
            value = findVal(object[k], key);
            return value !== undefined;
        }
    });
    return value;
}

var object =  { photo: { progress: 20 }};
console.log(findVal(object, 'progress'));

2 of 13
9

Your code has a few errors:

  • You're recursively calling util.findVal but not returning the result of the call. Code should be return util.findVal(...)
  • You're not passing the attribute name key to the recursive call
  • You're not handling the possibility of a reference loop
  • If an object contains a key and also a sub-object that contains the key which value is returned is random (depends on the sequence in which the keys are analyzed)

The third problem is what can cause infinite recursion, for example:

var obj1 = {}, obj2 = {};
obj1.x = obj2; obj2.y = obj1;

if you just keep looking recursively searching in obj1 or obj2 could lead to infinite recursion.

Unfortunately for reasons not clear to me in Javascript is impossible to know the object "identity"... (what Python id(x) does) you can only compare an object to another. This means that to know if an object has already been seen in the past you need a linear scan with known objects.

ES6 added the possibility to check object identity with Set and Map where objects can be used as keys. This allows for faster (sub-linear) search times.

A search solution that runs in depth order could be for example:

function findVal(obj, key) {
    var seen = new Set, active = [obj];
    while (active.length) {
        var new_active = [], found = [];
        for (var i=0; i<active.length; i++) {
            Object.keys(active[i]).forEach(function(k){
                var x = active[i][k];
                if (k === key) {
                    found.push(x);
                } else if (x && typeof x === "object" &&
                           !seen.has(x)) {
                    seen.add(x);
                    new_active.push(x);
                }
            });
        }
        if (found.length) return found;
        active = new_active;
    }
    return null;
}

given an object and an attribute name, returns all the values found with that name at the first depth they are found (there can be more than one value: for example when searching {x:{z:1}, y:{z:2}} for the key "z" two values are at the same depth).

The function also correctly handles self-referencing structures avoiding infinite search.

🌐
GitHub
gist.github.com › clochix › 3958723
Recursive search in JavaScript · GitHub
August 9, 2013 - Clone this repository at &lt;script src=&quot;https://gist.github.com/clochix/3958723.js&quot;&gt;&lt;/script&gt; Save clochix/3958723 to your computer and use it in GitHub Desktop. Download ZIP · Recursive search in JavaScript · Raw · deepSearch.js · This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below.
🌐
GoLinuxCloud
golinuxcloud.com › home › javascript › search in json object recursively in javascript [solved]
Search in JSON Object Recursively in JavaScript [SOLVED] | GoLinuxCloud
January 18, 2023 - If the recursive call returns a value other than undefined, the value is returned; otherwise, the loop continues until all properties have been searched. To use this function, we can pass in a JSON object and the key we want to find:
🌐
Mikael Lundin Weblog
blog.mikaellundin.name › 2018 › 09 › 02 › recursively-search-and-update-a-json-javascript-object.html
Recursively Search and Update a JSON/JavaScript Object — Mikael Lundin Weblog
September 2, 2018 - The gist here is not about reading or parsing CSV or JSON, but rather how to search and update a big javascript object. If it were XML, I would just have used XSLT and be done in a snap. Let's look at the end result. This code will recurse through an object graph, looking for an object with matching ID property. If it finds it, it will run the update function on that object.
🌐
Go Make Things
gomakethings.com › recursion-with-vanilla-javascript
Recursion with vanilla JavaScript | Go Make Things
January 8, 2020 - var levelsUp = function (elem, ... recursively run levelsUp() again return levelsUp(parent, selector, distance); }; The levelsUp() method will run multiple times until it finds a match or hits the window element, and will return ...
🌐
Medium
medium.com › @natelapinski › recursive-array-methods-better-javascript-through-haskell-62c47b02d08c
Recursive Array Methods. Better Javascript through Haskell. | by Nate Lapinski | Medium
December 30, 2019 - The goal is to give you some insight into how map, filter, and reduce work, and to demonstrate how to the think about array operations in Javascript in terms of recursion, similar to how Haskell and other pure languages operate on lists.
Top answer
1 of 1
3

Initially, I considered suggesting using Array.filter() but then that wouldn't allow breaking out of the loop once a selected item was found.

One approach is to use Array.some() or Array.find() (and disregard the return value) with a ternary operator. A variable can be initialized as the first line, then the .some() callback returns the result of setting that variable to either the current item if it is selected, otherwise the return value of the recursive call.

function findSelectedInList(list) {
  var selected;
  list.some(function(currentItem) {
    return selected = currentItem.selected === "selected" ? currentItem : findSelectedInList(currentItem.children);
  });
  return selected;
}

And one could use ecmascript-6 features like arrow functions and the let keyword), to condense it slightly:

function findSelectedInList(list){
  let selected;
  list.some((currentItem) => selected = currentItem.selected === "selected" ? currentItem : findSelectedInList(currentItem.children));
  return selected;
}

To see this demonstrated, expand the snippet below.

var treeList = [{
  "roleName": "User",
  "roleId": "role1",
  "children": [{
    "roleName": "subUser1",
    "roleId": "role11",
    "children": [],
    "selected": "selected"
  }]
}];


var treeList2 = [{
  "roleName": "User",
  "roleId": "role1",
  "children": [],
  "selected": "selected"
}];
var treeList3 = [{
  "roleName": "User",
  "roleId": "role1",
  "children": []
}];

function findSelectedInList(list) {
  var selected;
  list.some(function(currentItem) {
    return selected = currentItem.selected === "selected" ? currentItem : findSelectedInList(currentItem.children);
  });
  return selected;
}
console.log('treeList: ', findSelectedInList(treeList));
console.log('treeList2: ', findSelectedInList(treeList2));
console.log('treeList3: ', findSelectedInList(treeList3));

I know it doesn't really cover the find or some methods, but these functional JS exercises are a good thing to go through.

🌐
Medium
medium.com › hackernoon › you-might-not-need-that-recursive-function-in-javascript-275651522185
You Might Not Need that Recursive Function in JavaScript | by Nick Scialli | HackerNoon.com | Medium
February 29, 2020 - In programming, we’re often faced with situations where the answer appears to require solving the same problem an indeterminate number of times. When we encounter a problem like this, we tend to reach for recursion — often accomplished by creating a function that calls itself as many times as necessary.
🌐
Medium
medium.com › @fullstacktips › how-to-search-for-a-specific-file-recursively-using-node-js-a6318d31f2fc
How to search for a specific file recursively using Node Js? | by FullStackTips | Medium
January 6, 2023 - To search for a specific file in the directories and subdirectories recursively in Node.js, you can use the fs (file system) module and the readdirSync() function. This function reads the contents of a directory, and returns an array of the ...