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
- SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
- Yup Date Format Validation With Moment JS
- Yup Number Validation: Allow Empty String
- Exactly Same Query Behaving Differently in Mongo Client and Mongoose
- JavaScript Unit Testing JSON Schema Validation
- Reduce JS Size With Constant Strings
- JavaScript SDK