1

Sorry if the title is a bit miss leading, I'll give an example of what I wish to do.

If I have a object say:

{
    testable: {
        some: {
           id: 10
        },
        another: {
            some: {
                id: 20
            }
        },
        an : {
           arrayExample: ['test']
        }
    }
}

and I want an easy way to get an array of all the keys for non object values like so:

['testable.some.id','testable.another.some.id', 'testable.an.arrayExample.0']

How would I do it? I assume I'd need a recursive function to build such an array as I'd have to do a for loop through each object and grab the key and add it to the previous namespace before pushing it into an array.

Also is this a bit to costly as a function? Especially if I'm going to use the array afterwards to grab the values.

Peter Fox
  • 1,809
  • 2
  • 20
  • 34

2 Answers2

2

You could use an iterative an recursive approach to get all keys of the object and array.

For all nested keys, you nedt to iterate over all items. There is no short circuit or other way around to skipt that task.

If you have some fancy names like 'item.0' you may get the wrong path to it.

Edit

Added a function getValue for getting a value from a path to a property/item. This works for falsy values as well.

function getPath(object) {
    function iter(o, p) {
        if (Array.isArray(o) ){
            o.forEach(function (a, i) {
                iter(a, p.concat(i));
            });
            return;
        }
        if (typeof o === 'object') {
            Object.keys(o).forEach(function (k) {
                iter(o[k], p.concat(k));
            });
            return;
        }
        path.push(p.join('.'));                
    }

    var path = [];
    iter(object, []);
    return path;
}

function getValue(object, path) {
    return path.split('.').reduce(function (r, a) {
        return (Array.isArray(r) || typeof r === 'object') ? r[a] : undefined;
    }, object);
}

var obj = { testable: { some: { id: 10, number: 0 }, another: { some: { id: 20 } }, an: { arrayExample: ['test'] } } },
    path = getPath(obj);

document.write('<pre>' + JSON.stringify(getPath(obj), 0, 4) + '</pre>');
path.forEach(function (a) {
    document.write(getValue(obj, a) + '<br>');
});

//console.log(getPath(obj));
//path.forEach(function (a) {
//    console.log(getValue(obj, a));
//});
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • This looks to be perfect for what I need, thank you. I plan to use a function like the answer to: http://stackoverflow.com/questions/17519495/search-object-with-namespace-key to retrieve the keys, I assume from the looks of it it won't have any issue getting the key from an object or array even – Peter Fox May 22 '16 at 13:39
  • @PeterFox, just try it :) – Nina Scholz May 22 '16 at 13:40
  • it works perfectly with the namespace accessor on even arrays, thank you! – Peter Fox May 22 '16 at 13:56
  • Console shows the expected result, but I'm getting: 4 x /**error accessing property**/ table ending with the row "test" within the snippet. Why is that?! – Bekim Bacaj May 22 '16 at 15:33
  • i see no errors. please test above. – Nina Scholz May 22 '16 at 15:41
0

That would simply be done by the invention of a "reusable" Object method. I call it Object.prototype.getNestedValue() It dynamically fetches the value resides at deeply nested properties. You have to provide the properties as arguments in an ordered fashion. So for example if you would like to get the value of myObj.testable.some.id you should invoke the function as var myId = myObj.getNestedValue("testable","some","id") // returns 10 So lets see how it works.

Object.prototype.getNestedValue = function(...a) {
  return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};

var myObj = {
    testable: {
        some: {
           id: 10
        },
        another: {
            some: {
                id: 20
            }
        },
        an : {
           arrayExample: ['test']
        }
    }
},

myId = myObj.getNestedValue("testable","some","id");
console.log(myId);

//or if you receive the properties to query in an array can also invoke it like

myId = myObj.getNestedValue(...["testable","some","id"]);
console.log(myId);

So far so good. Let's come to your problem. Once we have this nice tool in our hand what you want to do becomes a breeze. This time i insert your each query in an array and the spread operator is our friend. Check this out.

Object.prototype.getNestedValue = function(...a) {
  return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};

var myObj = {
    testable: {
        some: {
           id: 10
        },
        another: {
            some: {
                id: 20
            }
        },
        an : {
           arrayExample: ['test']
        }
    }
},

idCollection = [["testable","some","id"],["testable","another","some","id"],["testable","an","arrayExample",0]],
         ids = idCollection.reduce((p,c) => p.concat(myObj.getNestedValue(...c)),[]);
console.log(ids);

Of course the the argument we use for querying can be dynamic like

var a = "testable",
    b = "another",
    c = "some",
    d = "id",
 myId = myObj.getNestedValue(a,b,c,d);

and you can reuse this Object method everywhere including arrays since in JS arrays are objects and the have perfect access to the Object.prototype. All you need is to pass the index of the array instead of the property of an object.

Redu
  • 25,060
  • 6
  • 56
  • 76