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, 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, second gets the path where a key has the given value.

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

Compare a known path for an object in a single check, without the need of 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 little modification, the above code can be made to 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 both the above codes is that they only return the first path found for a given key or key/value.

See also