I ran into a situation where I need access to a javascript object from the server. The server returns the string name of the function or object and based on other metadata I will evaluate the object differently.
Originally I was evaluating (eval([string])) and everything was fine. Recently I was updating the function to not use eval for security peace of mind, and I ran into an issue with namespaced objects/functions.
Specifically I tried to replace an eval([name]) with a window[name] to access the object via the square bracket syntax from the global object vs eval.
But I ran into a problem with namespaced objects, for example:
var strObjName = 'namespace.serviceArea.function';
// if I do
var obj = eval(strObjName); // works
// but if I do
var obj = window[strObjName]; // doesn't work
Can anyone come up with a good solution to avoid the use of eval with namespaced strings?
You could split on . and resolve each property in turn. This will be fine so long as none of the property names in the string contain a . character:
var strObjName = 'namespace.serviceArea.function';
var parts = strObjName.split(".");
for (var i = 0, len = parts.length, obj = window; i < len; ++i) {
obj = obj[parts[i]];
}
alert(obj);
Just thought I'd share this because I made this the other day. I didn't even realize reduce was available in JS!
function getByNameSpace(namespace, obj) {
return namespace.split('.').reduce(function(a,b) {
if(typeof a == 'object') return a[b];
else return obj[a][b];
});
}
Hope someone finds this useful..
I came up with this:
function reval(str){
var str=str.split("."),obj=window;
for(var z=0;z<str.length;z++){
obj=obj[str[z]];
}
return obj;
}
eval("window.this.that");
reval("this.that"); //would return the object
reval("this.that")(); //Would execute
I have written a different solution, using recursion. I was using this to namespace the name attribute of input elements. Take, for example, the following:
<input type="number" name="testGroup.person.age" />
And let's say we set the value of that input to "25". We also have an object in our code somewhere:
var data = {
testGroup: {
person: {
name: null,
age: null,
salary: null
}
}
};
So the goal here is to automatically put the value in our input element into the correct position in that data structure.
I've written this little function to create a nested object based on whatever array of namespaces you pass to it:
var buildObject = function ( obj, namespaceArray, pos ) {
var output = {};
output[namespaceArray[pos]] = obj;
if ( pos > 0 ) {
output = buildObject(output, namespaceArray, pos-1);
}
return output;
};
How does it work? It starts at the end of the namespaceArray array, and creates an object with one property, the name of which is whatever is in the last slot in namespaceArray, and the value of which is obj. It then recurses, wrapping that object in additional objects until it runs out of names in namespaceArray. pos is the length of the namespaceArray array. You could just work it out in the first function call, something like if ( typeof(pos) === "undefined" ) pos = namespaceArray.length - 1, but then you'd have an extra statement to evaluate every time the function recursed.
First, we split the name attribute of the input into an array around the namespace separators and get the value of the input:
var namespaceArray = inputElement.name.split("."),
obj = inputElement.value;
// Usually you'll want to do some undefined checks and whatnot as well
Then we just call our function and assign the result to some variable:
var myObj = buildObject(obj, namespaceArray, namespaceArray.length - 1);
myObj will now look like this:
{
testGroup: {
person: {
age: 25
}
}
}
At this point I use jQuery's extend function to merge that structure back into the original data object:
data = $.extend(true, data, myObj);
However, merging two objects is not very difficult to do in framework-free JavaScript and there is plenty of existing code that gets the job done well.
I'm sure there are more efficient ways to get this done, but this method meets my needs well.
I know this has been answered satisfactory to the asker, but I recently had this issue, but with WRITING to the value. I came up with my own static class to handle reading and writing based on a string path. The default path separator is . but you can modify it to be anything, such as /. The code is also commented incase you wonder how it works.
(function(){
var o = {}, c = window.Configure = {}, seperator = '.';
c.write = function(p, d)
{
// Split the path to an array and assaign the object
// to a local variable
var ps = p.split(seperator), co = o;
// Iterate over the paths, skipping the last one
for(var i = 0; i < ps.length - 1; i++)
{
// Grab the next path's value, creating an empty
// object if it does not exist
co = (co[ps[i]])? co[ps[i]] : co[ps[i]] = {};
}
// Assign the value to the object's last path
co[ps[ps.length - 1]] = d;
}
c.read = function(p)
{
var ps = p.split(seperator), co = o;
for(var i = 0; i < ps.length; i++)
{
co = (co[ps[i]])? co[ps[i]] : co[ps[i]] = {};
}
return co;
}
})();
My sample on pastebin:
http://pastebin.com/13xUnuyV
i liked you code, and i did a lot of similar stuff using PHP.
But here that was tough as i though...
So i used answers #LordZardeck (https://stackoverflow.com/a/9338381/1181479) and #Alnitak (https://stackoverflow.com/a/6491621/1181479).
And here what i have, just use it:
var someObject = {
'part1' : {
'name': 'Part 1',
'size': '20',
'qty' : '50'
},
'part2' : {
'name': 'Part 2',
'size': '15',
'qty' : '60'
},
'part3' : [
{
'name': 'Part 3A РУКУ!!!',
'size': '10',
'qty' : '20'
}, {
'name': 'Part 3B',
'size': '5',
'qty' : '20'
}, {
'name': 'Part 3C',
'size': '7.5',
'qty' : '20'
}
]
};
//var o = {}, c = window.Configure = {}, seperator = '.';
var c = function(){
this.o = {};
this.seperator = ".";
this.set = function(obj){
this.o = obj;
}
this.write = function(p, d) {
p = p.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
p = p.replace(/^\./, ''); // strip leading dot
// Split the path to an array and assaign the object
// to a local variable
var ps = p.split(this.seperator), co = this.o;
// Iterate over the paths, skipping the last one
for(var i = 0; i < ps.length - 1; i++)
{
// Grab the next path's value, creating an empty
// object if it does not exist
co = (co[ps[i]])? co[ps[i]] : co[ps[i]] = {};
}
// Assign the value to the object's last path
co[ps[ps.length - 1]] = d;
}
this.read = function(p) {
p = p.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
p = p.replace(/^\./, ''); // strip leading dot
var ps = p.split(this.seperator), co = this.o;
/*
for(var i = 0; i < ps.length; i++)
{
co = (co[ps[i]])? co[ps[i]] : co[ps[i]] = {};
}
*/
while (ps.length) {
var n = ps.shift();
if (n in co) {
co = co[n];
} else {
return;
}
}
return co;
}
};
var n = new c();
n.set(someObject);
console.log('whas');
console.log('n.read part.name', n.read('part1.name'));
n.write('part3[0].name', "custom var");
console.log('part1.name now changed');
n.write('part1.name', "tmp");
console.log('n.read part.name', n.read('part1.name'));
console.log('----');
console.log('before', someObject);
console.log('someObject.part1.name', someObject.part1.name);
console.log('someObject.part3[0].name', someObject.part3[0].name);
It is possible to do this using functional programming techniques such as
(function (s) {return s.split('.').reduce(function(p,n) { p[n] = p[n] || {}; return p[n];},window);})("some.cool.namespace");
This can be assigned to a global function for re-use
window.ns = (function (s) {return s.split('.').reduce(function(p,n) { p[n] = p[n] || {}; return p[n];},window);})
Then we can do the following
ns("some.cool").namespace = 5;
if (5 != ns("some.cool.namespace")) { throw "This error can never happen" }
The following assumes that the parts of strObjName are separated by . and loops through starting at window until it gets down to the function you want:
var strObjParts = strObjName.split('.');
var obj = window;
for(var i in strObjParts) {
obj = obj[strObjParts[i]];
}
Related
I am working on some software that reads/writes information in localStorage using a handler. You can find a working example here: http://jsbin.com/wifucugoko/edit?js,console
My problem is with the segment of code below (focusing on the switch statement):
_t.set = function(path, value) { // Update a single value or object
if (~path.indexOf(".")) {
let o = path.split(".")[0],
p = this.get(o),
q = path.split(".").slice(1);
switch (q.length) {
// There has to be a better way to do this...
case 1:
p[q[0]] = value;
break;
case 2:
p[q[0]][q[1]] = value;
break;
case 3:
p[q[0]][q[1]][q[2]] = value;
break;
case 4:
p[q[0]][q[1]][q[2]][q[3]] = value;
break;
case 5:
p[q[0]][q[1]][q[2]][q[3]][q[4]] = value;
break;
case 6:
p[q[0]][q[1]][q[2]][q[3]][q[4]][q[5]] = value;
break;
default:
return "error";
}
b.setItem(o, JSON.stringify(p));
return p;
} else {
b.setItem(path, JSON.stringify(value));
return this.get(path);
}
};
I am not going to be the only one using this codebase, and I am trying to make it easy for others to update any value that could be placed in localStorage. Right now you can update a value by using something like local.set('item.subitem.proeprty', 'value') Though the code above does that, it's ugly and doesn't scale.
How can this method be improved to (1) update a property nested at any depth automatically, instead of writing an infinitely-long switch statement, and (2) not lace a parent object with [object Object] after a value is updated?
This question has nothing to do with my use of localStorage. I originally posted this question in code review, which requires a working contextual example. They closed this question immediately, since part of my problem is the code I provided doesn't work once you start dealing with updating a value nested more than six objects deep. Though I could have continued my switch statement indefinitely, that's exactly what I'm trying to avoid.
With the three examples provided you'll see that setting a value in one place doesn't remove values in other places:
local.set('user.session.timeout', false);
local.set('user.name', {first:'john', last:'doe', mi:'c'});
local.set('user.PIN', 8675309);
All these values, though set at different times, only UPDATE or create a value, they do NOT clear any pre-existing values elsewhere.
As for me the minimal optimization would be following:
if (~path.indexOf(".")) {
let o = path.split(".")[0],
p = this.get(o),
q = path.split(".").slice(1),
dist = p;
q.forEach(function(item, index) {
if (index < q.length - 1) {
dist = dist[item];
} else {
dist[item] = value;
}
});
b.setItem(o, JSON.stringify(p));
return p;
} else {
changed parts:
dist variable is created
hardcoded switch is replaced with foreach
You could try something like this, if the path does not exists, the value is null:
function retreiveValueFromObject(path, object) {
var pathList = path.split(".");
var currentValue = object;
var brokeEarly = false;
for (var i = 0; i < pathList.length; i++) {
if (currentValue[pathList[i]]) {
currentValue = currentValue[pathList[i]];
} else {
brokeEarly = true;
break;
}
}
return {
value: currentValue,
brokeEarly: brokeEarly
};
}
function setValueInObject(path, value, object) {
var nestedObj = retreiveValueFromObject(path, object).value;
var pathList = path.split(".");
var finalKey = pathList[pathList.length - 1];
nestedObj[finalKey] = value;
}
var someObject = {
a: {
c: {
d: "value"
},
z: "c"
},
b: {
f: {
x: "world"
},
g: "hello"
},
};
console.log(retreiveValueFromObject("b.f.x", someObject));
setValueInObject("b.f.y", "newValue", someObject);
console.log(someObject);
What you are looking for is a little bit of recursion, I just implemented the update method.
let localStorageHandler = function() {
let b = window.localStorage,
_t = this;
_t.get = function(a) {
try {
return JSON.parse(b.getItem(a))
} catch (c) {
return b.getItem(a)
}
};
function descendAndUpdate(obj, path, value) {
let current = path[0],
remainingPath = path.slice(1);
// found and update
if (obj.hasOwnProperty(current) && remainingPath.length === 0) {
obj[current] = value;
// found but still not there
} else if (obj.hasOwnProperty(current)) {
return descendAndUpdate(obj[current], remainingPath, value);
}
// if you want do add new properties use:
// obj[current] = value;
// in the else clause
else {
throw('can not update unknown property');
}
}
_t.set = function(path, value) { // Update a single value or object
if (~path.indexOf(".")) {
let o = path.split(".")[0],
p = this.get(o),
q = path.split(".").slice(1);
descendAndUpdate(p, q, value);
console.log(p);
b.setItem(o, JSON.stringify(p));
return p;
} else {
b.setItem(path, JSON.stringify(value));
return this.get(path);
}
};
_t.remove = function(a) { // removes a single object from localstorage
let c = !1;
a = "number" === typeof a ? this.key(a) : a;
a in b && (c = !0, b.removeItem(a));
return c
};
};
let local = new localStorageHandler();
// Create user and session info if it doesn't exist
let blankUser = new Object({
alias: '',
dob: '',
PIN: '',
level: 0,
name: {
first: '',
last: '',
mi:'',
},
session: {
token: '',
timeout: true,
lastChange: Date.now()
}
});
local.remove('user');
// Loads user data into localstorage
if (!local.get('user')) {
local.set('user', blankUser);
}
local.set('user.session.timeout', false);
local.set('user.name', {first:'john', last:'doe', mi:'c'});
local.set('user.PIN', 8675309);
// new property
// local.set('user.sunshine', { 'like': 'always' });
console.log(local.get('user'));
A friend of mine would always prefer stacks over recursion, which would be a second option. Anyway I agree with many of the comments here. You already know your domain model. Unless you have a very good reason for this approach spend more time on serializing and unserializing those objects in the database. I have the impression you would be able to work with your data in a more natural way because the aspect of updating fields in a database would be abstracted away.
I am working on a similar project at the moment. What I am doing is storing the data in something I called a WordMatrix (https://github.com/SamM/Rephrase/blob/master/WordMatrix.js), maybe you could use something like it in your solution.
My project is a WIP but the next step is literally to add support for localStorage. The project itself is a database editor that works with key => value stores. You can view the prototype for it here: (https://samm.github.io/Rephrase/editor.html)
Once I have implemented the localStorage aspect I will update this post.
Your topic reminds me one recent another topic.
Trying to enhance the answer I provided, I propose you these functions:
// Function to get a nested element:
function obj_get_path(obj, path) {
return path.split('.').reduce((accu, val) => accu[val] || 'Not found', obj);
}
// Function to set a nested element:
function obj_set_path(obj, path, value) {
var result = obj;
var paths = path.split('.');
var len = paths.length;
for (var i = 0; i < len - 1; i++) {
result = result[paths[i]] || {};
}
result[paths[len - 1]] = value;
return obj;
}
// Example object
var obj = {
name0: 'A name',
level0: {
name1: 'An other name',
level1: {
level2: {
name3: 'Name to be changed',
text3: 'Some other text'
}
}
}
}
// Use of the function
obj = obj_set_path(obj, 'level0.level1.level2.name3', 'Takit Isy');
obj = obj_set_path(obj, 'level0.level1.level2.new3', 'I’m a new element!');
var obj_level2 = obj_get_path(obj, 'level0.level1.level2');
// Consoling
console.log('Consoling of obj_level2:\n', obj_level2);
console.log('\nConsoling of full obj:\n', obj); // To see that the object is correct
⋅
⋅
⋅
We could also adapt the 2nd function in my above snippet so that it works for both get and set, depending of if "value" is set:
// We could also adapt the second function for both uses:
function obj_path(obj, path, value = null) {
var result = obj;
var paths = path.split('.');
var len = paths.length;
for (var i = 0; i < len - 1; i++) {
result = result[paths[i]] || {};
}
// Return result if there is no set value
if (value === null) return result[paths[len - 1]];
// Set value and return
result[paths[len - 1]] = value;
return obj;
}
// Example object
var obj = {
name0: 'A name',
level0: {
name1: 'An other name',
level1: {
level2: {
name3: 'Name to be changed',
text3: 'Some other text'
}
}
}
}
// Use of the function
obj = obj_path(obj, 'level0.level1.level2.name3', 'Takit Isy');
obj = obj_path(obj, 'level0.level1.level2.new3', 'I’m a new element!');
var obj_level2 = obj_path(obj, 'level0.level1.level2');
// Consoling
console.log('Consoling of obj_level2:\n', obj_level2);
console.log('\nConsoling of full obj:\n', obj); // To see that the object is correct
Hope it helps.
How about:
function parse(str) {
var arr = str.split('.');
return function(obj) {
return arr.reduce((o, i) => o[i], obj);
}
}
let foo = {
a: {
b: {
c: {
bar: 0
}
}
}
}
let c = parse('a.b.c')(foo);
console.log(c.bar);
c['bar'] = 1;
console.log(foo);
I am developing a general function inside a solution which would replace /endpoint/{item.id}/disable/{item.name} with /endpoint/123/disable/lorem where I pass the function the URL and item.
The item would be an object with keys id and name.
What would be the best way to find items with the structure {item.KEY} and replacing them with item.KEY?
the best way to solve this is passing a function to handle a regex.
I modified your parameters to make the mapping easier- I'm sure you can change this on your own if necessary
var url = '/endpoint/{id}/disable/{name}'; //I modified your parameters
var definition = { id: 123, name: 'lorem' };
url = url.replace(/{([^}]*)}/g, function (prop) {
var key = prop.substr(1, prop.length - 2);
if (definition[key])
return encodeURIComponent(definition[key]);
else
throw new Error('missing required property "' + key + '" in property collection');
});
//alert(url);
fiddle: https://jsfiddle.net/wovr4ct5/2/
If you don't want to use eval, maybe use something like this:
var item = {
id: 123,
KEY: "lorem"
};
function pick(o, s) {
s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
s = s.replace(/^\./, ''); // strip a leading dot
var a = s.split('.');
for (var i = 0, n = a.length; i < n; ++i) {
var k = a[i];
if (k in o) {
o = o[k];
} else {
return;
}
}
return o;
}
and then
str.replace(/{.*?}/g, function(match){ // match {...}
var path = match.substring(1,match.length-1); // get rid of brackets.
return pick(scope, path); //get value and replace it in string.
});
I have a list of objects all named in this fashion:
var p1 = {};
var p2 = {};
p1.name = "john";
p1.hobby = "collects stamps";
p2.name = "jane";
p2.hobby = "collects antiques";
I know how to loop through p1 and p2 to collect the properties, provided I know how many of these p object literals there are. Here's my problem, I don't always know how many of these p object literals there will be. Sometimes it goes up to p2, sometimes it goes up to p20.
Is there a way to loop through objects if I know they all share the same prefix?
Edit: I can't change how I'm getting the list of objects. It's given to me in that format...
If we make the following assumptions:
The objects are global
The number suffixes are sequential
...then the following works:
for (var i = 1; window["p" + i] !== undefined; i++) {
console.log(window["p" + i]); // loop over each object here
}
You should have them in an Array referenced by a single variable.
var p = [];
p.push({
name:"john",
hobby:"collects stamps"
}, {
name:"jane",
hobby:"collects antiques"
});
Then you'd loop the Array, and enumerate each object...
for( var i = 0; i < p.length; i++ ) {
for( var n in p[i] ) {
console.log( p[i][n] );
}
}
EDIT:
It seems from a comment that these may be arriving as individual variable.
If they're global variables, and if they always have the same p1 naming, then you can access them as properties of the global window object.
var obj;
for( var i = 1; obj = window['p' + i]; i++ ) {
if( typeof obj === 'object' ) {
for( var n in obj ) {
console.log( obj[n] );
}
}
}
This loop will run until a p(n) global returns a falsey value.
So as long as a truthy value is found, and its typeof is 'object', you'll iterate that object.
If you have all your data stored in a variable , or a few variables you can push it into the array.
var data = "....JSON";
var a = [];
a.push(data);
Push keeps adding stuff into the array in basic sense.
You could also pop to remove the last pushed data.
Take a look at the other methods here:
http://www.w3schools.com/jsref/jsref_obj_array.asp
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array
Why don't you just store them all in one top-level object literal? It will make it easier to enumerate through them.
EG:
var MyObj = {
p1: {},
p2: {}
};
etc..
[edit]
If they are local vars, are you can't change the format of this data, you might have to use eval.
Don't shoot me:
var p1 = {};
var p2 = {};
p1.name = "john";
p1.hobby = "collects stamps";
p2.name = "jane";
p2.hobby = "collects antiques";
var found = true, c = 1;
while(found) {
try {
var obj = eval('p' + c);
c++;
console.log(obj);
} catch(e){
found = false;
}
}
I don't suggest using this, I suggest changing the format of the data you are receiving, but this is one possible solution.
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 property may be undefined (so mediaGroup may not be set).
What is a simple way to say:
if( entry.mediaGroup[0].contents[0].url ){
console.log( entry.mediaGroup[0].contents[0].url )
}
without generating an error? This way will generate an undefined error if any point along the way is undefined.
My solution
if(entry) && (entry.mediaGroup) && (entry.MediaGroup[0]) ...snip...){
console.log(entry.mediaGroup[0].contents[0].url)
}
which is pretty lengthy. I am guessing there must be something more elegant.
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
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 :)
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!
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;
}
I have a nested Javascript object like
var data = { 'name': { 'heading': 'Name', 'required': 1, 'type': 'String' },
'profile': {
'age': { 'heading': 'Age', 'required': 0, 'type': 'Number' },
'phone': { 'heading': 'Phone', 'required': 0, 'type': 'String'},
'city': { 'heading': 'City', 'required': 0, 'type': 'String'},
},
'status': { 'heading': 'Status', 'required': 1, 'type': 'String' }
};
Here, I can access the fields as data.profile.age.type or data.name.type. No Issues
And if I have dynamic variable names, I can access as below. Again, No Problems.
f = 'profile'; data[f].age.type
But, here I have variable names like 'name', 'profile.age', 'profile.city' etc and obviously I cannot access them as f = 'profile.age'; data[f].type which will not work.
Can anyone guide me how to access them (get/set) in the most straight-forward and simple way?
Note: I tried this and it works for get.
data.get = function(p) { o = this; return eval('o.'+p); };
f = 'profile.age'; data.get(f).name;
though set does not seem to be simple enough. Please let me know, if there are better solutions for get and set as well.
Don't use eval unless absolutely necessary. :) At least in this case, there are better ways to do it -- you can split the nested name into individual parts and iterate over them:
data.get = function(p) {
var obj = this;
p = p.split('.');
for (var i = 0, len = p.length; i < len - 1; i++)
obj = obj[p[i]];
return obj[p[len - 1]];
};
data.set = function(p, value) {
var obj = this;
p = p.split('.');
for (var i = 0, len = p.length; i < len - 1; i++)
obj = obj[p[i]];
obj[p[len - 1]] = value;
};
You can just nest the brackets:
var a = 'name', b = 'heading';
data[a][b]; // = `Name`
Perhaps a function that takes in the path to the property you're interested in and breaks it up into tokens representing properties. Something like this (this is very rough, of course):
data.get = function(path) {
var tokens = path.split('.'), val = this[tokens[0]];
if (tokens.length < 2) return val;
for(var i = 1; i < tokens.length; i++) {
val = val[tokens[i]];
}
return val;
}
example:
var f = 'one.two';
var data = { one: {two:'hello'}};
data.get = /* same as above */;
var val = data.get(f);
A clean way to access/set nested values is using reduce in ES6:
const x = ['a', 'b', 'c'], o = {a: {b: {c: 'tigerking'}}}
// Get value: "tigerking"
console.log(x.reduce((a, b) => a[b], o))
// Set value:
x.slice(0, x.length-1).reduce((a, b) => a[b], o)[x[x.length-1]] = 'blossom'
console.log(o) // {a: {b: {c: 'blossom'}}}
So, you can first convert your variable 'profile.age' to an array using 'profile.age'.split('.'), then use the approach above.
This uses the jquery.each() function to traverse down a json tree using a string variable that may or may not contain one or more "."'s. You could alternatively pass in an array and omit the .split();
pathString = something like "person.name"
jsonObj = something like {"person" : {"name":"valerie"}}.
function getGrandchild(jsonObj, pathString){
var pathArray = pathString.split(".");
var grandchild = jsonObj;
$.each(pathArray, function(i, item){
grandchild = grandchild[item];
});
return grandchild;
};
returns "valerie"