Reference Javascript object from string
Given data similar to:
var data = [{id: 12345,
name:'my products',
items:[{
size: 'XXL',
sku: 'awe2345',
开发者_JAVA百科 prices:[{type: 'rrp',prices: 10.99},
{type: 'sell_price', price: 9.99},
{type:'dealer', price:4.50}
]
},{
size: 'XL',
sku: 'awe2346',
prices:[{type: 'rep', prices: 10.99},
{type: 'sell_price', price: 9.99},
{type:'dealer', price:4.50}
]
}
]
}]
}]
is there a way to evaluate a string representation of an element in the data object? for example: "data[0].items[0].prices[0].rrp" ...without using eval()?
The ideal solution would be to not have the string representation in the first place. Seriously ask yourself whether you change the existing code to give you the content in a more ideal output.
However, if you can't, this should do what you want:
var path = "data[0].items[0].prices[0].rrp".split(/[\[\]\.]+/);
var next = window;
if (path[path.length - 1] == "") {
path.pop();
};
while (path.length && (next = next[path.shift()]) && typeof next == "object" && next !== null);
next;
Create a function for this:
function get(path) {
var next = window;
path = path.split(/[\[\]\.]+/);
if (path[path.length - 1] == "") {
path.pop();
};
while (path.length && (next = next[path.shift()]) && typeof next === "object" && next !== null);
return path.length ? undefined : next;
}
Downsides:
The variable
data
must be in the global scope for this to work (cannot be a local variable).It's skanky. Use it as a last resort.
Edit: To use setting functionality, you can abuse the pass-by-reference nature of JavaScript objects as follows:
function set(path, value) {
var split = Math.max(path.lastIndexOf("["), path.lastIndexOf("."));
get(path.slice(0, split))[path.slice(split + 1).replace(/\]/, "")] = value;
}
To provide some explanation of how this works:
The getter first splits the input into an array, so that each element is a member we need to traverse [data, 0, items, 0, prices, 0, rrp]
. If the search string ends with a "]", we get an extra empty-element at the end of the array, so we check for that and remove it.
We then do the big loop; so whist we have elements to traverse (while path.length
), set the next
variable to the next object member we need to traverse next = next[path.shift()]
. Check that it's an object (otherwise it wont have any members to traverse), and check that it isn't null (because typeof null == "object").
Once the loop has executed, we'll have the final element in the chain; so return it.
The setter searches for the last object reference in the search string, and retrieves that object reference using the get()
function. It then sets the returned objects key to the desired value. If we hadn't done it this way, we'd have been setting a value rather than a reference, so the "real" object would never get updated.
Matt's solution is pure genius - however there is some room for improvement:
- The solution fails if the 'path' has no separators;
set('h1', somevalue)
will fail because the splitting of the path fails in that case - Only global scope is supported.
- If you have an object with
obj.here.is.a.value = "I am a value"
and you ask forobj.here.is.a.value.no.value
- it will still return "I am a value"
Here's my fix for these three issues:
getPropertyValueByPath : function(obj, path)
{
path = path.split(/[\[\]\.]+/);
if(path[path.length - 1] == "")
{
path.pop();
};
while(path.length && ( obj = obj[path.shift()]));
return obj;
}
setPropertyValuebyPath : function(obj, path, value)
{
var pathElements = path.replace(/\[|\]/g, '.').replace(/\.+/g, '.').split(/\./)
pathEnd = pathElements[pathElements.length - 1]
pathRoot = (pathElements.slice(0, pathElements.length - 1).join('.'))
var currObj = obj
for(var i = 0; i < pathElements.length; i++)
{
if( typeof (currObj[pathElements[i]]) == 'undefined')
{
currObj[pathElements[i]] = {}
}
currObj = currObj[pathElements[i]]
}
// This line by Matt is genious :)
getPropertyValueByPath(obj, pathRoot)[pathEnd] = value
return true
}
call them like this:
var joe = {}
setPropertyValueByPath(joe,'the[1].long.and[2].road', 'yeah')
// joe.the[1].long.and[2].road now has a value of 'yeah' -
// all the missing objects were created
// this will alert 'yeah'
alert( getPropertyValueByPath(joe,'the[1].long.and[2].road') )
I'm not sure if I understand that question, but I guess you want to "select" that object within the inner Array ?
If thats the case, you need to loop over that inner Array.
Object.keys( data[0].items[0].prices[0] ).some(function( obj ) {
if( obj.type === 'rrp' ) {
// do something with that object here
return true;
}
});
To have that code ES3 compliant, we would go for:
var target = data[0].items[0].prices[0],
len = target.length;
for(var i = 0; i < len; i++) {
if( target[i].type === 'rrp' ) {
// do something with target[i]
break;
}
}
If I got the question wrong, doh sorry for your time :-)
If you use a getter instead of an exposed public field, you can make your way there doing some post-processing before the first return of your collection:
var data = [{
id: 12345,
name: 'my products',
items: [{
size: 'XXL',
sku: 'awe2345',
prices: [{ type: 'rrp', price: 10.99 },
{ type: 'sell_price', price: 9.99 },
{ type: 'dealer', price: 4.50 }
]
}, {
size: 'XL',
sku: 'awe2346',
prices: [{ type: 'rep', price: 10.99 },
{ type: 'sell_price', price: 9.99 },
{ type: 'dealer', price: 4.50}]
}
],
getItems: function () {
if (!this._applied) {
this._applied = true;
for (var i = 0; i < this.items.length; i++) {
this.items[i].prices = this._asCrossReferencable(this.items[i].prices);
}
}
return this.items;
},
_asCrossReferencable: function (priceArr) {
for (var i = 0; i < priceArr.length; i++) {
priceArr[i][priceArr[i].type] = priceArr[i].price;
}
return priceArr;
},
_applied: false
}];
var items = data[0].getItems();
for(var i = 0; i < items.length; i++) {
for(var p = 0; p < items[i].prices.length; p++){
var price = items[i].prices[p];
alert(price[price.type]);
}
}
That's a lot of bloat though just for some lazy shorthand.
精彩评论