JavaScript Find Path of Key in Deeply Nested Object or Array

Key Path Finder Using Depth First Search (DFS)

The following two codes go through the whole object, an array of objects, or a collection of both to get the path to a particular key. There are two versions: first gets the path to the key only, and second gets the path where a key has the given value.

A similar recursive solution as below can also be used to modify all the keys of a deeply nested object or array of objects.

1. Get Path to the Nested Key

Code

const findPath = (ob, key) => {
  const path = [];
  const keyExists = (obj) => {
    if (!obj || (typeof obj !== "object" && !Array.isArray(obj))) {
      return false;
    }
    else if (obj.hasOwnProperty(key)) {
      return true;
    }
    else if (Array.isArray(obj)) {
      let parentKey = path.length ? path.pop() : "";

      for (let i = 0; i < obj.length; i++) {
        path.push(`${parentKey}[${i}]`);
        const result = keyExists(obj[i], key);
        if (result) {
          return result;
        }
        path.pop();
      }
    }
    else {
      for (const k in obj) {
        path.push(k);
        const result = keyExists(obj[k], key);
        if (result) {
          return result;
        }
        path.pop();
      }
    }
    return false;
  };

  keyExists(ob);

  return path.join(".");
}

Examples

const deeplyNestedObj = {
  a: {
    b: {
      c: {
        d: {
          e: "e",
          f: "f",
          g: {
            G: undefined,
            h: {
              i: {},
              j: {
                k: {
                  K: null,
                  l: {
                    abc: 123,                   
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  e1: {},
  f2: {},
  P1: {
    q1: {
      r1: "r1"
    }
  }
}

console.log(findPath(deeplyNestedObj, "K")); // => a.b.c.d.g.h.j.k
console.log(findPath(deeplyNestedObj, "r1")); // => P1.q1
console.log(findPath(deeplyNestedObj, "gibberish")); // => ""
console.log(findPath(deeplyNestedObj, "helloWorld")); // => ""

Use Case

Compare a known path for an object in a single check without needing multiple && comparisons or a try-catch block. Example:

const errorResponse = {
  status: 403,
  data: {
    user: {
      errorMessage: "You're not authorized to update this user."
    }
  }
};

if(findPath(errorResponse, "errorMessage") === "data.user"){ // true
  //...
} 

2. Get Path to the Nested Key With Given Value

With slight modification, we can make the above code compare the value given for the key.

const findPath = (ob, key, value) => {
  const path = [];
  const keyExists = (obj) => {
    if (!obj || (typeof obj !== "object" && !Array.isArray(obj))) {
      return false;
    }
    else if (obj.hasOwnProperty(key) && obj[key] === value) {
      return true;
    }
    else if (Array.isArray(obj)) {
      let parentKey = path.length ? path.pop() : "";

      for (let i = 0; i < obj.length; i++) {
        path.push(`${parentKey}[${i}]`);
        const result = keyExists(obj[i], key);
        if (result) {
          return result;
        }
        path.pop();
      }
    }
    else {
      for (const k in obj) {
        path.push(k);
        const result = keyExists(obj[k], key);
        if (result) {
          return result;
        }
        path.pop();
      }
    }

    return false;
  };

  keyExists(ob);

  return path.join(".");
}

const deeplyNestedFamilyTree = [ // starts with array
  {
    name: "John",
    children: [{
      name: "Dora",
      children: [{
        name: "Sara"
      }, {
        name: "James"
      }]
    }]
  },
  {
    name: "Scott",
    children: [{
      name: "Smith",
      children: [{
        name: "Brad"
      }, {
        name: "David"
      }]
    }]
  }
]

const deeplyNestedOrgChart = { // starts with object
  position: "CEO",
  subordinates: [
    {
      position: "CFO"
    },
    {
      position: "CTO",
      subordinates: [
        {
          position: "Engineering Lead"
        }
      ]
    },
    {
      position: "COO"
    }
  ]
}


console.log(findPath(deeplyNestedFamilyTree, "name", "John")); 
// => [0]

console.log(findPath(deeplyNestedFamilyTree, "name", "James")); 
// => [0].children[0].children[1]

console.log(findPath(deeplyNestedFamilyTree, "name", "Scott")); 
// => [1]

console.log(findPath(deeplyNestedOrgChart, "position", "Engineering Lead"));
// => subordinates[1].subordinates[0]

Limitation

The limitation of the above codes is that they only return the first path found for a given key or key/value pair.

See also

When you purchase through links on techighness.com, I may earn an affiliate commission.