tutorial // Oct 01, 2021

How to Recursively Traverse an Object with JavaScript

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.

How to Recursively Traverse an Object with JavaScript

Getting Started

For this tutorial, we're going to create a simple, single file Node.js project. On your computer, choose a good location for your file (e.g., a projects folder) and create a file called index.js.

Next, make sure that you've installed Node.js on your computer. While the code we write will not depend on Node.js to work, we'll need it to run or execute the code we write inside of index.js.

Once you have your file created and Node.js installed, we're ready to get started.

Creating a function to match objects by key and value

An easy way to understand the concept of recursion is to think of a spiral staircase in a house. In order to go from the top of the staircase to the bottom, you need to walk down one step at a time.

Though you do it automatically, technically you have a "function" in your brain that tells you how to walk down one step at a time until you reach the bottom. You call that "function" for each step in the staircase until there are no more steps. As you walk down, you tell the "function" to call itself again if there's a step after the current one.

This is how recursion works in JavaScript (or any programming language). You write a function that performs a task and have that function call itself again if it hasn't met some requirement—for example, finding a nested value or reaching the end of a list.

For this tutorial, we're going to write a function that focuses on the former: finding a nested object. More specifically, we want to write a recursive function that finds a nested object containing a specific key with a specific value.

First, let's create our base function and explain what it's up to:

/index.js

const findNestedObject = (object = {}, keyToMatch = "", valueToMatch = "") => {
  // We'll implement our function here...
};

Our function will take three arguments: an object to traverse, a keyToMatch within that object, and a valueToMatch within that object.

/index.js

const isObject = (value) => {
  return !!(value && typeof value === "object" && !Array.isArray(value));
};

const findNestedObject = (object = {}, keyToMatch = "", valueToMatch = "") => {
  if (isObject(object)) {
    // We'll work on finding our nested object here...
  }

  return null;
};

Next, in order to avoid any runtime errors, in the body of our findNestedObject function, we add an if statement with a call to a new function we've added above isObject(), passing in the object argument that was passed to findNestedObject.

Looking at isObject(), we want to be certain that the object we're traversing is actually an object. To find out, we need to verify that the passed value is not null or undefined, has a typeof "object," and it is not an array. That last one may look odd. We need to do !Array.isArray() because in JavaScript, Arrays have a typeof "object" (meaning that our previous typeof value === "object" test can be "fooled" by an array being passed).

Assuming that isObject() returns true for the value we passed it, we can start to traverse the object. If not, as a fallback, from our findNestedObject() function we return null to signify that we did not find a match.

/index.js

const isObject = (value) => {
  return !!(value && typeof value === "object" && !Array.isArray(value));
};

const findNestedObject = (object = {}, keyToMatch = "", valueToMatch = "") => {
  if (isObject(object)) {
    const entries = Object.entries(object);

    for (let i = 0; i < entries.length; i += 1) {
      const [treeKey, treeValue] = entries[i];

      if (treeKey === keyToMatch && treeValue === valueToMatch) {
        return object;
      }
    }
  }

  return null;
};

Adding some complexity, now, we want to begin the traversal of our object. By "traverse" we mean looping over each key/value pair on the object passed in to findNestedObject().

To do that loop, we first call to Object.entries() passing in our object. This will return us an array of arrays, where each array contains the key of the key/value pair currently being looped over as the first element and the value of the key/value pair currently being looped over as the second element. Like this:

const example = {
  first: 'thing',
  second: 'stuff',
  third: 'value',
};

Object.entries(example);

[
  ['first', 'thing'],
  ['second', 'stuff'],
  ['third', 'value']
]

Next, with our array of key/value pairs (entries), we add a for loop to iterate over the array. Here, i will equal the index of the current key/value pair we're looping over. We want to do that until we've looped over all entires so we say "run this loop while i < entries.length and for each iteration, and 1 to the current index i."

Inside of the for loop, we use JavaScript array destructuring to access the current key/value pair array (denoted by entries[i]), assigning each a variable. Here, we assign the first element to the variable objectKey and the second element to the variable objectValue.

Remember: our goal is to find an object by the passed keyToMatch and valueToMatch. In order to find a match, we need to check each key and value on our object to see if they're a match. Here, assuming that we find a match, we return the object as it fulfilled the requirement of having the keyToMatch and valueToMatch.

Adding recursion to traverse objects of an arbitrary depth

Now for the fun part. Right now, our function can only loop over a single-level depth object. This is great, but remember, we want to search for a nested object. Because we don't know where that object might be in the "tree" (a nickname you will occasionally hear for an object of nested objects), we need to be able to "keep going" if one of the values in the key/value pairs is itself an object.

This is where our recursion comes in.

/index.js

const isObject = (value) => {
  return !!(value && typeof value === "object" && !Array.isArray(value));
};

const findNestedObject = (object = {}, keyToMatch = "", valueToMatch = "") => {
  if (isObject(object)) {
    const entries = Object.entries(object);

    for (let i = 0; i < entries.length; i += 1) {
      const [objectKey, objectValue] = entries[i];

      if (objectKey === keyToMatch && objectValue === valueToMatch) {
        return object;
      }

      if (isObject(objectValue)) {
        const child = findNestedObject(objectValue, keyToMatch, valueToMatch);

        if (child !== null) {
          return child;
        }
      }
    }
  }

  return null;
};

Remember our staircase analogy from earlier. At this point, we've only walked down one step. In order to step down to the next step, we need to tell our function to call itself again.

In this case, we know there is another "step" or object to traverse if passing objectValue to the isObject() function we set up earlier returns true. If it does, that means that we need to check if that object contains the keyToMatch and valueToMatch we're looking for.

To traverse that object, we recursively (meaning, to call the function we're currently inside of again), passing in the objectValue along with the original keyToMatch and keyToValue (what we're looking for hasn't changed, just the object we want to look at).

If our recursive call finds a match (meaning our recursive call to findNestedObject() does not return null), we return that object child. Assuming that our recursive call to findNestedObject() did not return a match, our traversal would stop. If our child itself had nested objects (keeping with our analogy, another "step" to walk down), again, we'd call findNestedObject().

Because this code is recursive, it will run until it either finds a matching object, or, exhausts the available nested objects to search.

Now for a test. Let's try to find the object in this tree with a name field equal to "Down here!"

/index.js

const isObject = (value) => {
  return !!(value && typeof value === "object" && !Array.isArray(value));
};

const findNestedObject = (object = {}, keyToMatch = "", valueToMatch = "") => {
  if (isObject(object)) {
    const entries = Object.entries(object);

    for (let i = 0; i < entries.length; i += 1) {
      const [objectKey, objectValue] = entries[i];

      if (objectKey === keyToMatch && objectValue === valueToMatch) {
        return object;
      }

      if (isObject(objectValue)) {
        const child = findNestedObject(objectValue, keyToMatch, valueToMatch);

        if (child !== null) {
          return child;
        }
      }
    }
  }

  return null;
};

const staircase = {
  step: 5,
  nextStep: {
    step: 4,
    nextStep: {
      step: 3,
      nextStep: {
        step: 2,
        nextStep: {
          name: "Down here!",
          step: 1,
        },
      },
    },
  },
};

const match = findNestedObject(staircase, "name", "Down here!");
console.log(match);
// { name: "Down here!", step: 1 }

const match2 = findNestedObject(staircase, "step", 3);
console.log(match2);
// { step: 3, nextStep: { step: 2, nextStep: { name: "Down here!", step: 1 } } }

Here's a quick demo of this running in real-time:

Wrapping up

In this tutorial, we learned how to recursively traverse an object using JavaScript. We learned how to create a base function that was able to loop over the keys of an object we passed it, looking for a matching key and value pair. Then, we learned how to use that function recursively, calling it from within itself if the value of the key/value pair we were currently looping over was an object.

Written By
Ryan Glover

Ryan Glover

CEO/CTO @ CheatCode