2

I’ve got a deep nested JavaScript Object and I know a value which is at the object’s lowest position. I want to know the parent key and grandparent key.

I set up a jsbin https://jsbin.com/yimugezuxe/edit?js,console

Based on the value 'uniqueID#9aserdf' I want to know/return its parent keys: 'Stage 2' and 'Level 2' from the object below:

const obj = {
    "Level 1": {
        "Stage 1": [
        {
            "title": "Parent 1",
            "id": "Parent1#id",
            "Children": [
            {
                "title": "Steve",
                "id": "uniqueID1"
            },
            {
                "title": "James",
                "id": "uniqueID#9"
            }
            ]
        }
        ]
    },
    "Level 2": {
        "Stage 1": [
        {
            "title": "Parent 4",
            "id": "Parent4#id",
            "Children": [
            {
                "title": "Tim",
                "id": "uniqueIDadsf"
            },
            {
                "title": "Hans",
                "id": "uniqueID#9asdf"
            }
            ]
        }
        ],
        "Stage 2": [
        {
            "title": "Parent 10",
            "id": "Parent10#id",
            "Children": [
            {
                "title": "Chris",
                "id": "uniqueIDadsf33"
            },
            {
                "title": "Jack",
                "id": "uniqueID#9aserdf"
            }
            ]
        }
        ]
    }
};

// based on the value 'uniqueID#9aserdf' I want to know/return its parent keys: 'Stage 2' and 'Level 2'
Kalaschnik
  • 769
  • 1
  • 7
  • 21
  • 5
    Please post the code you've already tried so we can assist you. – Jeff Hechler Aug 29 '19 at 13:26
  • I was trying a for..in loop but since it is nested at second level I got confused. Do I need nested for..in loop? – Kalaschnik Aug 29 '19 at 13:31
  • Finding the element is covered in [Find by key deep in a nested object](https://stackoverflow.com/q/15523514/215552) – Heretic Monkey Aug 29 '19 at 13:39
  • Possible duplicate of [Javascript - Find path to object reference in nested object](https://stackoverflow.com/questions/43636000/javascript-find-path-to-object-reference-in-nested-object) – Heretic Monkey Aug 29 '19 at 13:41
  • @HereticMonkey It is a different structure. Sorry. I’m really struggling to adapt that. – Kalaschnik Aug 29 '19 at 13:56
  • 2
    The answers are independent of structure. I've posted an answer which adapts the two linked answers with almost no change to the code... – Heretic Monkey Aug 29 '19 at 14:09
  • @HereticMonkey The duplicate version does indeed work independently: https://stackoverflow.com/a/43636296/2258480 — Sorry and Thank You – Kalaschnik Aug 29 '19 at 15:16

4 Answers4

3

Something like this:

for (var prop in obj) {
  for (var prop2 in obj[prop]) {

  }
}

Then use this method to iterate down and check

Here is a full example:

for (var prop in obj) {
  for (var prop2 in obj[prop]) {
    //console.log(prop2);
    for (var prop3 in obj[prop][prop2]) {
      //console.log("->"+prop3);
      for (var prop4 in obj[prop][prop2][prop3]) {
        //console.log("->"+"->"+prop4);
        for (var prop5 in obj[prop][prop2][prop3][prop4]) {
          //console.log("->"+"->"+"->"+prop5);
          for (var prop6 in obj[prop][prop2][prop3][prop4][prop5]) {
            //console.log("->"+"->"+"->"+"->"+prop6);
            if (obj[prop][prop2][prop3][prop4][prop5][prop6] == "uniqueID#9aserdf") {
              console.log("Stage :" +prop2 + " == Level :" + prop);
            }
          }
        }
      }
    }
  }
}

you can take away the comments in the code, to help see where you are in the object.

krisph
  • 639
  • 1
  • 10
  • 14
  • Okay, going for a nested loop is what I thought. Yet I thought there is something more elegant to solve this (aka functional)? – Kalaschnik Aug 29 '19 at 13:33
  • can’t go further down then stage ...`for (let level in obj) { for (let stage in obj[level]) { console.log(stage) } }` – Kalaschnik Aug 29 '19 at 13:51
3

Try this code:

It uses recursion and will provide the parent nodes.

const obj = {
    "Level 1": {
        "Stage 1": [{
            "title": "Parent 1",
            "id": "Parent1#id",
            "Children": [{
                    "title": "Steve",
                    "id": "uniqueID1"
                },
                {
                    "title": "James",
                    "id": "uniqueID#9"
                }
            ]
        }]
    },
    "Level 2": {
        "Stage 1": [{
            "title": "Parent 4",
            "id": "Parent4#id",
            "Children": [{
                    "title": "Tim",
                    "id": "uniqueIDadsf"
                },
                {
                    "title": "Hans",
                    "id": "uniqueID#9asdf"
                }
            ]
        }],
        "Stage 2": [{
            "title": "Parent 10",
            "id": "Parent10#id",
            "Children": [{
                    "title": "Chris",
                    "id": "uniqueIDadsf33"
                },
                {
                    "title": "Jack",
                    "id": "uniqueID#9aserdf"
                }
            ]
        }]
    }
};

let path = [];


function getParentAndGrandParent(path, json, value) {

    for (var key in json) {

        if (typeof json[key] === 'object') {
            path.push(key.toString());
            // console.log("PAth : " + path)
            getParentAndGrandParent(path, json[key], value);
            path.pop();
        } else {
            if (json[key] == value) {
                console.log("Parent : " + path);
            }
        }
    }

}

getParentAndGrandParent(path, obj, 'uniqueID#9asdf')
Saurabh Bishnoi
  • 614
  • 6
  • 10
  • That is very elegant, however, if I try an return statement it is somehow undefined: So instead of: `console.log("Parent : " + path);` I did this: `return path;` – Kalaschnik Aug 29 '19 at 14:11
  • @Kalaschnik can try to create a promise and resolve the value from it eg: resolve(path) – Saurabh Bishnoi Aug 29 '19 at 14:43
2

Here it is a function that returns a tuple with the Level and the Stage and if there is no matching ChildrenId returns [null, null].

It is a bit messy but I haven't found an easy way to do it.

const findAncestors = (obj, ChildrenId) => {

    let stageResult = null;

    const result = Object.entries(obj).find(level => {
        const validLevel = Object.values(level[1]).find((stage, index) => {
            const validId = stage[0].Children.some(children => {
                return children.id === ChildrenId
            })
            if (validId) {
                stageResult = Object.keys(level[1])[index];
            }
            return validId;
        })
        return validLevel != undefined;
    })

    return stageResult === null ? [null, null] : [result[0], stageResult];
}
gbarcomu
  • 46
  • 4
0

I modified getObject from this answer by Zach to Find by key deep in a nested object to accept a property name and value.

I pulled findPath directly from this answer by ibrahim mahrir to Javascript - Find path to object reference in nested object.

Combining them, you can get the object which contains the property your are looking for, then use that to get the path to that object. You can alter findPath to return an array of strings and pull just the first two if that's all you want.

function getObject(theObject, propName, propValue) {
    var result = null;
    if(theObject instanceof Array) {
        for(var i = 0; i < theObject.length; i++) {
            result = getObject(theObject[i], propName, propValue);
            if (result) {
                break;
            }   
        }
    }
    else
    {
        for(var prop in theObject) {
            //console.log(prop + ': ' + theObject[prop]);
            if(prop == propName) {
                if(theObject[prop] == propValue) {
                    return theObject;
                }
            }
            if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
                result = getObject(theObject[prop], propName, propValue);
                if (result) {
                    break;
                }
            } 
        }
    }
    return result;
}

function findPath(a, obj) {
    for(var key in obj) {                                         // for each key in the object obj
        if(obj.hasOwnProperty(key)) {                             // if it's an owned key
            if(a === obj[key]) return key;                        // if the item beign searched is at this key then return this key as the path
            else if(obj[key] && typeof obj[key] === "object") {   // otherwise if the item at this key is also an object
                var path = findPath(a, obj[key]);                 // search for the item a in that object
                if(path) return key + "." + path;                 // if found then the path is this key followed by the result of the search
            }
        }
    }
}

const data = {
    "Level 1": {
        "Stage 1": [
        {
            "title": "Parent 1",
            "id": "Parent1#id",
            "Children": [
            {
                "title": "Steve",
                "id": "uniqueID1"
            },
            {
                "title": "James",
                "id": "uniqueID#9"
            }
            ]
        }
        ]
    },
    "Level 2": {
        "Stage 1": [
        {
            "title": "Parent 4",
            "id": "Parent4#id",
            "Children": [
            {
                "title": "Tim",
                "id": "uniqueIDadsf"
            },
            {
                "title": "Hans",
                "id": "uniqueID#9asdf"
            }
            ]
        }
        ],
        "Stage 2": [
        {
            "title": "Parent 10",
            "id": "Parent10#id",
            "Children": [
            {
                "title": "Chris",
                "id": "uniqueIDadsf33"
            },
            {
                "title": "Jack",
                "id": "uniqueID#9aserdf"
            }
            ]
        }
        ]
    }
};
const o = getObject(data, "id", "uniqueID#9aserdf");
console.log(o);
const path = findPath(o, data);
console.log(path);
Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122