skip to Main Content

I have an array of objects with items (only have name property) and groups (with a children property, they may contain items or other groups) and I need to get a full path to needle value, so in this case it’d be myObj[2]["children"][0]["children"][1]["children"][0], plus I’m limited to quite old JS version ECMA 262 (I’m using it inside Photoshop)

var myObj = [
{
    "name": "group1",
    "children": [
    {
        "name": "group2",
        "children": [
        {
            "name": "item0"
        }]
    }]
},
{
    "name": "item1"
},
{
    "name": "needleGroup",
    "children": [
    {
        "name": "needleNestedGroup",
        "children": [
        {
            "name": "item3"
        },
        {
            "name": "needleNestedDeeperGroup",
            "children": [
            {
                "name": "needle"
            }]
        }]
    }]
}];

My first idea was to transform object to array or arrays so it’d be easier to process, so my object became

[
    [
        [
            "item0"
        ]
    ],
    "item1", 
    [
        [
            "item3", 
            [
                "needle"
            ]
        ]
    ]
];

But.. that’s it. I can’t figure out hot to track down only the indexes I need. Could you please point out a correct direction.

4

Answers


  1. Use a recursive function to look for the item you want. Once the function find it, it will return an array. Each step back of the recursion will unshift the object key of this step:

    function find(obj, item) {
        for(var key in obj) {                                   // for each key in obj (obj is either an object or an array)
            if(obj[key] && typeof obj[key] === "object") {      // if the current property (value of obj[key]) is also an object/array
                var result = find(obj[key], item);              // try finding item in that object
                if(result) {                                    // if we find it
                    result.unshift(key);                        // we shift the current key to the path array (result will be an array of keys)
                    return result;                              // and we return it to our caller
                }
            } else if(obj[key] === item) {                      // otherwise (if obj[key] is not an object or array) we check if it is the item we're looking for
                return [key];                                   // if it is then we return the path array (this is where the path array get constructed)
            }
        }
    }
    

    The output of this function will be an array of keys leading to item. You can easily transform it to the format in the question:

    function findFormatted(obj, item) {
        var path = find(obj, item);                             // first let's get the path array to item (if it exists)
        if(path == null) {                                      // if it doesn't exist
            return "";                                          // return something to signal its inexistance
        }
        return 'myObj["' + path.join('"]["') + '"]';            // otherwise format the path array into a string and return it
    }
    

    Example:

    function find(obj, item) {
        for(var key in obj) {
            if(obj[key] && typeof obj[key] === "object") {
                var result = find(obj[key], item);
                if(result) {
                    result.unshift(key);
                    return result;
                }
            } else if(obj[key] === item) {
                return [key];
            }
        }
    }
    
    function findFormatted(obj, item) {
        var path = find(obj, item);
        if(path == null) {
            return "";
        }
        return 'myObj["' + path.join('"]["') + '"]';
    }
    
    var myObj = [{"name":"group1","children":[{"name":"group2","children":[{"name":"item0"}]}]},{"name":"item1"},{"name":"needleGroup","children":[{"name":"needleNestedGroup","children":[{"name":"item3"},{"name":"needleNestedDeeperGroup","children":[{"name":"needle"}]}]}]}];
    
    console.log("find(myObj, "needle"):          " + JSON.stringify(find(myObj, "needle")));
    console.log("findFormatted(myObj, "needle"): " + findFormatted(myObj, "needle"));

    Note: The indexes for the arrays are also formatted as strings, but that won’t be a problem as someArray["2"] is equivalent to someArray[2].

    Login or Signup to reply.
  2. Assuming that you have a nested and repetitive pattern of objects in your data-set, the following solution would do the trick for you.

    const nodePath = { value: [] };
    
    function findTreeNodeAndPath(
      targetNodeKey,
      targetNodeValue,
      nodeChildrenKey,
      tree
    ) {
      if (tree[targetNodeKey] === targetNodeValue) {
        nodePath.value.push(tree);
        return;
      }
    
      if (tree[nodeChildrenKey].length > 0) {
        tree[nodeChildrenKey].forEach(children => {
          if (nodePath.value.length < 1) {
            findTreeNodeAndPath(
              targetNodeKey,
              targetNodeValue,
              nodeChildrenKey,
              children
            );
          }
        });
      } else if (tree[nodeChildrenKey].length === 0) {
        return;
      }
    
      if (nodePath.value.length > 0) {
        nodePath.value.push(tree);
      }
    }
    
    const exampleData = {
      name: "Root",
      children: [
        {
          name: "A2",
          children: [
            {
              name: "AC1",
              children: [
                {
                  name: "ACE1",
                  children: []
                }
              ]
            },
            {
              name: "AC2",
              children: [
                {
                  name: "ACF1",
                  children: []
                },
                {
                  name: "ACF2",
                  children: [
                    {
                      name: "ACFG1",
                      children: []
                    }
                  ]
                },
                {
                  name: "ACF3",
                  children: [
                    {
                      name: "ACFH1",
                      children: []
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    };
    
    findTreeNodeAndPath("name", "ACFG1", "children", exampleData);
    console.log(nodePath.value)

    The recursive part of the code will iterate on the children of the current node. The existing strategy here is depending on the nodePath.value having at least one element, which indicates that it found the targetted node. Later on, it’ll skip the remaining nodes and would break out of recursion.

    The nodePath.value variable will give you the node-to-root path.

    Login or Signup to reply.
  3. I’ve created something you might use. The code below returns an Array of paths to keys, values, objects you are looking for.

    See snippet and example to see what you can do.

    To make it work you have to pass key and/or value you want to find in element and element which is an Array or Object.

    It’s written in newer JS standard but it shouldn’t be a problem to compile it to older standard.

    function findKeyValuePairsPath(keyToFind, valueToFind, element) {
      if ((keyToFind === undefined || keyToFind === null) &&
        (valueToFind === undefined || valueToFind === null)) {
        console.error('You have to pass key and/or value to find in element!');
        return [];
      }
    
      const parsedElement = JSON.parse(JSON.stringify(element));
      const paths = [];
    
      if (this.isObject(parsedElement) || this.isArray(parsedElement)) {
        checkObjOrArr(parsedElement, keyToFind, valueToFind, 'baseElement', paths);
      } else {
        console.error('Element must be an Object or Array type!', parsedElement);
      }
    
      console.warn('Main element', parsedElement);
      return paths;
    }
    
    function isObject(elem) {
      return elem && typeof elem === 'object' && elem.constructor === Object;
    }
    
    function isArray(elem) {
      return Array.isArray(elem);
    }
    
    function checkObj(obj, keyToFind, valueToFind, path, paths) {
      Object.entries(obj).forEach(([key, value]) => {
        if (!keyToFind && valueToFind === value) {
          // we are looking for value only
          paths.push(`${path}.${key}`);
        } else if (!valueToFind && keyToFind === key) {
          // we are looking for key only
          paths.push(path);
        } else if (key === keyToFind && value === valueToFind) {
          // we ale looking for key: value pair
          paths.push(path);
        }
    
        checkObjOrArr(value, keyToFind, valueToFind, `${path}.${key}`, paths);
      });
    }
    
    function checkArr(array, keyToFind, valueToFind, path, paths) {
      array.forEach((elem, i) => {
        if (!keyToFind && valueToFind === elem) {
          // we are looking for value only
          paths.push(`${path}[${i}]`);
        }
        checkObjOrArr(elem, keyToFind, valueToFind, `${path}[${i}]`, paths);
      });
    }
    
    function checkObjOrArr(elem, keyToFind, valueToFind, path, paths) {
      if (this.isObject(elem)) {
        checkObj(elem, keyToFind, valueToFind, path, paths);
      } else if (this.isArray(elem)) {
        checkArr(elem, keyToFind, valueToFind, path, paths);
      }
    }
    
    const example = [
      {
        exampleArr: ['lol', 'test', 'rotfl', 'yolo'],
        key: 'lol',
      },
      {
        exampleArr: [],
        key: 'lol',
      },
      {
        anotherKey: {
          nestedKey: {
            anotherArr: ['yolo'],
          },
          anotherNestedKey: 'yolo',
        },
        emptyKey: null,
        key: 'yolo',
      },
    ];
    
    console.log(findKeyValuePairsPath('key', 'lol', example)); // ["baseElement[0]", "baseElement[1]"]
    console.log(findKeyValuePairsPath(null, 'yolo', example)); // ["baseElement[0].exampleArr[3]", "baseElement[2].anotherKey.nestedKey.anotherArr[0]", "baseElement[2].anotherKey.anotherNestedKey", "baseElement[2].key"]
    console.log(findKeyValuePairsPath('anotherNestedKey', null, example)); //["baseElement[2].anotherKey"]
    Login or Signup to reply.
  4. I came accross this issue and took the chance to create find-object-paths, which solves this problem: Finding paths in an object by either keys, values or key/value combinations.

    NPM: find-object-paths

    Github: getPaths

    Example object:

    {
              "company": {
                "name": "ACME INC",
                "address": "1st Street, Toontown, TT",
                "founded": "December 31st 1969",
                "hasStocks": true,
                "numberOfEmployees": 2,
                "numberOfActors": 3
              },
              "employees": [
                {
                  "employeeNumber": 1,
                  "name": "Hugo Boss",
                  "age": 65,
                  "isCEO": true
                },
                {
                  "employeeNumber": 2,
                  "name": "Herbert Assistant",
                  "age": 32,
                  "isCEO": false
                }
              ],
              "actors": [
                {
                  "actorId": 1,
                  "name": "Bugs Bunny",
                  "retired": false,
                  "playedIn": [
                    {
                      "movie": "Who Framed Roger Rabbit",
                      "started": 1988
                    },
                    {
                      "movie": "Space Jam",
                      "started": 1996
                    },
                    {
                      "movie": "Looney Tunes: Back in Action",
                      "started": 2003
                    }
                  ]
                },
                {
                  "actorId": 2,
                  "name": "Pepé le Pew",
                  "retired": true,
                  "playedIn": [
                    {
                      "movie": "For Scent-imental Reasons",
                      "started": 1949
                    }
                  ]
                },
                {
                  "actorId": 3,
                  "name": "Marvin the Martian",
                  "retired": true,
                  "playedIn": [
                    {
                      "movie": "Marvin the Martian in the Third Dimension",
                      "started": 1996
                    },
                    {
                      "movie": "Duck Dodgers and the Return of the 24½th Century",
                      "started": 1980
                    },
                    {
                      "movie": "Hare-Way to the Stars",
                      "started": 1958
                    }
                  ]
                },
                {
                  "actorId": 4,
                  "name": "Yosemite Sam",
                  "retired": false,
                  "playedIn": [
                    {
                      "movie": "Who Framed Roger Rabbit",
                      "started": 1988
                    },
                    {
                      "movie": "Space Jam",
                      "started": 1996
                    },
                    {
                      "movie": "Looney Tunes: Back in Action",
                      "started": 2003
                    }
                  ]
                }
              ],
              "distributionChannels": [
                "Celluloyd",
                [
                  "VHS",
                  "Betamax",
                  "DVD",
                  "Blueray"
                ],
                [
                  "channel",
                  12,
                  true
                ]
              ]
        }
    

    So, the basic usage could be:

    import { findObjectPaths } from 'findObjectPaths';
    
    class TestMe {
    
        static async main() {
            let acmeInc = {};
            rawFileContent = fs.readFileSync(p.resolve(__dirname, 'acmeInc.json'), 'utf-8');
            acmeInc = JSON.parse(rawFileContent);
    
            let path = findObjectPaths(acmeInc, {key: 'founded'});
            // company.founded
            path = findObjectPaths(acmeInc, {value: 'December 31st 1969'});
            // company.founded
    
            const allPaths: string[] = findObjectPaths(acmeInc, {key: 'actorId'}) as string[];
            /* [ 'actors[0].actorId',
                 'actors[1].actorId',
                 'actors[2].actorId',
                 'actors[3].actorId' ]
             */
    
            const ceoPath = findObjectPaths(acmeInc, {key: 'isCEO', value: true});
             // employees[0].isCEO
        }
    }
    
    TestMe.main();
    

    See the full documentation here: https://github.com/maugenst/getPaths#readme

    BR

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search