In javascript, lets say I want to access a property deep in an object, for example:
entry.mediaGroup[0].contents[0].url
At any point along that structure, a
Here's the one i have been using for a while
var obj = { a: { b: [
{ c: {d: 'XYZ'} }
] } };
// working
obj.a.b[0].c.d = null;
console.log('value:'+getProperty(obj, 'a.b[0].c.d', 'NOT-AVAILABLE')); // value:null
obj.a.b[0].c.d = 'XYZ';
console.log('value:'+getProperty(obj, 'a.b[0].c.d', 'NOT-AVAILABLE')); // value:XYZ
console.log('value:'+getProperty(obj, 'a.b[0].c.d.k.sds', 'NOT-AVAILABLE')); // value:NOT-AVAILABLE
obj.a.b[0].c = null;
console.log('value:'+getProperty(obj, 'a.b[0].c.d', 'NOT-AVAILABLE')); // value:NOT-AVAILABLE
// will not work
//console.log('v:'+getProperty(obj, 'a.b["0"].c.d'));
Here's the function
function getProperty(obj, str, defaultValue){
var props = str.split('.').map(function(prop){
var arrAccessRegEx = /(.*)\[(.*)\]/g;
if (arrAccessRegEx.test(prop)){
return prop.split(arrAccessRegEx).filter(function(ele){return ele!=''; });
} else {
var retArr = [];
retArr.push(prop);
return retArr
};
});
//console.log(props);
for(var i=0;i<props.length;i++){
var prop = props[i][0];
//console.log('prop:'+prop);
if (obj === null) return defaultValue;
obj = obj[prop];
if (obj === undefined) return defaultValue;
if (props[i].length == 2){
var idx = props[i][1];
if (!(obj instanceof Array)) return defaultValue;
if (idx < obj.length ){
obj = obj[idx];
if (obj === undefined) return defaultValue;
}
}
} // for each item in split
return obj;
}
There are probably 3-4 different questions along this vein, and four times as many answers. None of them really satisfied me, so I made my own, and I'll share it.
This function is called "deepGet".
Example:
deepGet(mySampleData, "foo.bar[2].baz", null);
Here is the full code:
function deepGet (obj, path, defaultValue) {
// Split the path into components
var a = path.split('.');
// If we have just one component left, note that for later.
var last = (a.length) === 1;
// See if the next item is an array with an index
var myregexp = /([a-zA-Z]+)(\[(\d+)\])+/; // matches: item[0]
var match = myregexp.exec(a[0]);
// Get the next item
var next;
if (match !== null) {
next = obj[match[1]];
if (next !== undefined) {
next = next[match[3]];
}
} else {
next = obj[a[0]];
}
if (next === undefined || next === null) {
// If we don't have what we want, return the default value
return defaultValue;
} else {
if (last) {
// If it's the last item in the path, return it
return next;
} else {
// If we have more items in the path to go, recurse
return deepGet (next, a.slice(1).join("."), defaultValue);
}
}
}
Here is a jsFiddle: http://jsfiddle.net/7quzmjh8/2/
I was inspired by these two things:
http://designpepper.com/blog/drips/making-deep-property-access-safe-in-javascript.html http://jsfiddle.net/wxrzM/1/
Hopefully this is useful to someone out there :)
This is a very lazy way to do it, but it meets the criteria for many similar situations:
try {
console.log(entry.mediaGroup[0].contents[0].url);
} catch (e) {}
This should not be done on long code blocks where other errors may potentially be ignored, but should be suitable for a simple situation like this.
/*decend through an object tree to a specified node, and return it.
If node is unreachable, return undefined. This should also work with arrays in the tree.
Examples:
var test1 = {a:{b:{c:{d:1}}}};
console.log(objectDesend(test1, 'a', 'b', 'c', 'd'));
var test2 = {a:{b:{c:1}}}; //will fail to reach d
console.log(objectDesend(test2, 'a', 'b', 'c', 'd'));
*/
var objectDescend = function(){
var obj = arguments[0];
var keys = arguments;
var cur = obj;
for(var i=1; i<keys.length; i++){
var key = keys[i];
var cur = cur[key];
if(typeof(cur)=='undefined')
return cur;
}
return cur;
}
var test1 = {a:{b:{c:{d:1}}}};
console.log(objectDescend(test1, 'a', 'b', 'c', 'd'));
var test2 = {a:{b:{c:1}}};
console.log(objectDescend(test2, 'a', 'b', 'c', 'd'));
So this will return either the value you are looking for, or undefined since that value doesn't exist. It won't return false, as that may actually be the value you are looking for (d:false).
In my code base, I add Object.prototype.descend, so I can do test1.descend('a', 'b', 'c', 'd'). This will only work in ECMAScript 5 (IE>=9) since you need to make it so your function doesn't appear in enumerations. For more info: Add a method to Object primative, but not have it come up as a property
Here is my code for that:
Object.defineProperty(Object.prototype, 'descend', {
value: function(){
var keys = arguments;
var cur = this;
for(var i=0; i<keys.length; i++){
var key = keys[i];
var cur = cur[key];
if(typeof(cur)=='undefined')
return cur;
}
return cur;
}
});
var test1 = {a:{b:{c:{d:false}}}};
//this will return false, which is the value of d
console.log(test1.descend('a', 'b', 'c', 'd'));
var test2 = {a:{b:{c:1}}};
//undefined since we can't reach d.
console.log(test2.descend(test2, 'a', 'b', 'c', 'd'));
Your current solution is probably as good as you can get, as mVChr says, try..catch is just lazy here. It's probably far less effient and has nothing to recommend it other than perhaps being easier to type (but not significantly so) and it'll be harder to debug as it silently hides errors.
The real issue is the very long "reference worm" created by attempting such access. An alternative to the original that at least reduces the number of property lookups is:
var o;
if ( (o = entry ) &&
(o = o.mediaGroup) &&
(o = o[0] ) &&
(o = o.contents ) &&
(o = o[0] )) {
alert(o.url);
}
But I expect you won't like that.
If you have many such deep access paths, you might like to create a function to do the access and return the last object on success or some other vaule on failure. For failure, you could also have it return the last non-falsey object on the path.
// Create test object
var entry = {};
entry.mediaGroup = [{
contents: [{url: 'url'}]
}];
// Check that it "works"
// alert(entry.mediaGroup[0].contents[0].url);
// Deep property access function, returns last object
// or false
function deepAccess(obj) {
var path = arguments;
var i = 0, iLen = path.length;
var o = path[i++]; // o is first arg
var p = path[i++]; // p is second arg
// Go along path until o[p] is falsey
while (o[p]) {
o = o[p];
p = path[i++];
}
// Return false if didn't get all the way along
// the path or the last non-falsey value referenced
return (--i == iLen) && o;
}
// Test it
var x = deepAccess(entry, 'mediaGroup','0','contents','0');
alert(x && x.url); // url
var x = deepAccess(entry, 'mediaGroup','1','contents','0');
alert(x && x.url); // false
I use this simple function for playing around with deep object properties:
getProperty = function(path) {
try {
return eval(path);
}
catch (e) {
return undefined;
}
};
Here's an example:
var test = {a:{b:{c:"success!"}}};
alert(getProperty('test.c.c'));
// undefined
alert(getProperty('test.a.b.c'));
// success!