Access object child properties using a dot notation string [duplicate] - javascript

This question already has answers here:
Accessing nested JavaScript objects and arrays by string path
(44 answers)
Closed 7 years ago.
I'm temporarily stuck with what appears to be a very simple JavaScript problem, but maybe I'm just missing the right search keywords!
Say we have an object
var r = { a:1, b: {b1:11, b2: 99}};
There are several ways to access the 99:
r.b.b2
r['b']['b2']
What I want is to be able to define a string
var s = "b.b2";
and then access the 99 using
r.s or r[s] //(which of course won't work)
One way is to write a function for it that splits the string on dot and maybe recursively/iteratively gets the property. But is there any simpler/more efficient way? Anything useful in any of the jQuery APIs here?

Here's a naive function I wrote a while ago, but it works for basic object properties:
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
while(arr.length && (obj = obj[arr.shift()]));
return obj;
}
console.log(getDescendantProp(r, "b.b2"));
//-> 99
Although there are answers that extend this to "allow" array index access, that's not really necessary as you can just specify numerical indexes using dot notation with this method:
getDescendantProp({ a: [ 1, 2, 3 ] }, 'a.2');
//-> 3

split and reduce while passing the object as the initalValue
Update
(thanks to comment posted by TeChn4K)
With ES6 syntax, it is even shorter
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var value = s.split('.').reduce((a, b) => a[b], r);
console.log(value);
Old version
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var value = s.split('.').reduce(function(a, b) {
return a[b];
}, r);
console.log(value);

You can use lodash get() and set() methods.
Getting
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// → 3
Setting
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// → 4

If it's possible in your scenario that you could put the entire array variable you're after into a string you could use the eval() function.
var r = { a:1, b: {b1:11, b2: 99}};
var s = "r.b.b2";
alert(eval(s)); // 99
I can feel people reeling in horror

Extending #JohnB's answer, I added a setter value as well. Check out the plunkr at
http://plnkr.co/edit/lo0thC?p=preview
function getSetDescendantProp(obj, desc, value) {
var arr = desc ? desc.split(".") : [];
while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
// handle arrays
if ((match !== null) && (match.length == 3)) {
var arrayData = {
arrName: match[1],
arrIndex: match[2]
};
if (obj[arrayData.arrName] !== undefined) {
if (typeof value !== 'undefined' && arr.length === 0) {
obj[arrayData.arrName][arrayData.arrIndex] = value;
}
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}
continue;
}
// handle regular things
if (typeof value !== 'undefined') {
if (obj[comp] === undefined) {
obj[comp] = {};
}
if (arr.length === 0) {
obj[comp] = value;
}
}
obj = obj[comp];
}
return obj;
}

This is the simplest i could do:
var accessProperties = function(object, string){
var explodedString = string.split('.');
for (i = 0, l = explodedString.length; i<l; i++){
object = object[explodedString[i]];
}
return object;
}
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var o = accessProperties(r, s);
alert(o);//99

you could also do
var s = "['b'].b2";
var num = eval('r'+s);

Here is an extension of Andy E's code, that recurses into arrays and returns all values:
function GetDescendantProps(target, pathString) {
var arr = pathString.split(".");
while(arr.length && (target = target[arr.shift()])){
if (arr.length && target.length && target.forEach) { // handle arrays
var remainder = arr.join('.');
var results = [];
for (var i = 0; i < target.length; i++){
var x = this.GetDescendantProps(target[i], remainder);
if (x) results = results.concat(x);
}
return results;
}
}
return (target) ? [target] : undefined; //single result, wrap in array for consistency
}
So given this target:
var t =
{a:
{b: [
{'c':'x'},
{'not me':'y'},
{'c':'z'}
]
}
};
We get:
GetDescendantProps(t, "a.b.c") === ["x", "z"]; // true

I don't know a supported jQuery API function but I have this function:
var ret = data; // Your object
var childexpr = "b.b2"; // Your expression
if (childexpr != '') {
var childs = childexpr.split('.');
var i;
for (i = 0; i < childs.length && ret != undefined; i++) {
ret = ret[childs[i]];
}
}
return ret;

I've extended Andy E's answer, so that it can also handle arrays:
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
//while (arr.length && (obj = obj[arr.shift()]));
while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
if ((match !== null) && (match.length == 3)) {
var arrayData = { arrName: match[1], arrIndex: match[2] };
if (obj[arrayData.arrName] != undefined) {
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}
} else {
obj = obj[comp]
}
}
return obj;
}
There are probably more efficient ways to do the Regex, but it's compact.
You can now do stuff like:
var model = {
"m1": {
"Id": "22345",
"People": [
{ "Name": "John", "Numbers": ["07263", "17236", "1223"] },
{ "Name": "Jenny", "Numbers": ["2", "3", "6"] },
{ "Name": "Bob", "Numbers": ["12", "3333", "4444"] }
]
}
}
// Should give you "6"
var x = getDescendantProp(model, "m1.People[1].Numbers[2]");

Performance tests for Andy E's, Jason More's, and my own solution are available at http://jsperf.com/propertyaccessor. Please feel free to run tests using your own browser to add to the data collected.
The prognosis is clear, Andy E's solution is the fastest by far!
For anyone interested, here is the code for my solution to the original question.
function propertyAccessor(object, keys, array) {
/*
Retrieve an object property with a dot notation string.
#param {Object} object Object to access.
#param {String} keys Property to access using 0 or more dots for notation.
#param {Object} [array] Optional array of non-dot notation strings to use instead of keys.
#return {*}
*/
array = array || keys.split('.')
if (array.length > 1) {
// recurse by calling self
return propertyAccessor(object[array.shift()], null, array)
} else {
return object[array]
}
}

Short answer: No, there is no native .access function like you want it. As you correctly mentioned, you would have to define your own function which splits the string and loops/checks over its parts.
Of course, what you always can do (even if its considered bad practice) is to use eval().
Like
var s = 'b.b2';
eval('r.' + s); // 99

Here is a a little better way then #andy's answer, where the obj (context) is optional, it falls back to window if not provided..
function getDescendantProp(desc, obj) {
obj = obj || window;
var arr = desc.split(".");
while (arr.length && (obj = obj[arr.shift()]));
return obj;
};

Related

How to access nested computed properties in javascript? [duplicate]

This question already has answers here:
Accessing nested JavaScript objects and arrays by string path
(44 answers)
Closed 7 years ago.
I'm temporarily stuck with what appears to be a very simple JavaScript problem, but maybe I'm just missing the right search keywords!
Say we have an object
var r = { a:1, b: {b1:11, b2: 99}};
There are several ways to access the 99:
r.b.b2
r['b']['b2']
What I want is to be able to define a string
var s = "b.b2";
and then access the 99 using
r.s or r[s] //(which of course won't work)
One way is to write a function for it that splits the string on dot and maybe recursively/iteratively gets the property. But is there any simpler/more efficient way? Anything useful in any of the jQuery APIs here?
Here's a naive function I wrote a while ago, but it works for basic object properties:
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
while(arr.length && (obj = obj[arr.shift()]));
return obj;
}
console.log(getDescendantProp(r, "b.b2"));
//-> 99
Although there are answers that extend this to "allow" array index access, that's not really necessary as you can just specify numerical indexes using dot notation with this method:
getDescendantProp({ a: [ 1, 2, 3 ] }, 'a.2');
//-> 3
split and reduce while passing the object as the initalValue
Update
(thanks to comment posted by TeChn4K)
With ES6 syntax, it is even shorter
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var value = s.split('.').reduce((a, b) => a[b], r);
console.log(value);
Old version
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var value = s.split('.').reduce(function(a, b) {
return a[b];
}, r);
console.log(value);
You can use lodash get() and set() methods.
Getting
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// → 3
Setting
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// → 4
If it's possible in your scenario that you could put the entire array variable you're after into a string you could use the eval() function.
var r = { a:1, b: {b1:11, b2: 99}};
var s = "r.b.b2";
alert(eval(s)); // 99
I can feel people reeling in horror
Extending #JohnB's answer, I added a setter value as well. Check out the plunkr at
http://plnkr.co/edit/lo0thC?p=preview
function getSetDescendantProp(obj, desc, value) {
var arr = desc ? desc.split(".") : [];
while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
// handle arrays
if ((match !== null) && (match.length == 3)) {
var arrayData = {
arrName: match[1],
arrIndex: match[2]
};
if (obj[arrayData.arrName] !== undefined) {
if (typeof value !== 'undefined' && arr.length === 0) {
obj[arrayData.arrName][arrayData.arrIndex] = value;
}
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}
continue;
}
// handle regular things
if (typeof value !== 'undefined') {
if (obj[comp] === undefined) {
obj[comp] = {};
}
if (arr.length === 0) {
obj[comp] = value;
}
}
obj = obj[comp];
}
return obj;
}
This is the simplest i could do:
var accessProperties = function(object, string){
var explodedString = string.split('.');
for (i = 0, l = explodedString.length; i<l; i++){
object = object[explodedString[i]];
}
return object;
}
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var o = accessProperties(r, s);
alert(o);//99
you could also do
var s = "['b'].b2";
var num = eval('r'+s);
Here is an extension of Andy E's code, that recurses into arrays and returns all values:
function GetDescendantProps(target, pathString) {
var arr = pathString.split(".");
while(arr.length && (target = target[arr.shift()])){
if (arr.length && target.length && target.forEach) { // handle arrays
var remainder = arr.join('.');
var results = [];
for (var i = 0; i < target.length; i++){
var x = this.GetDescendantProps(target[i], remainder);
if (x) results = results.concat(x);
}
return results;
}
}
return (target) ? [target] : undefined; //single result, wrap in array for consistency
}
So given this target:
var t =
{a:
{b: [
{'c':'x'},
{'not me':'y'},
{'c':'z'}
]
}
};
We get:
GetDescendantProps(t, "a.b.c") === ["x", "z"]; // true
I don't know a supported jQuery API function but I have this function:
var ret = data; // Your object
var childexpr = "b.b2"; // Your expression
if (childexpr != '') {
var childs = childexpr.split('.');
var i;
for (i = 0; i < childs.length && ret != undefined; i++) {
ret = ret[childs[i]];
}
}
return ret;
I've extended Andy E's answer, so that it can also handle arrays:
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
//while (arr.length && (obj = obj[arr.shift()]));
while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
if ((match !== null) && (match.length == 3)) {
var arrayData = { arrName: match[1], arrIndex: match[2] };
if (obj[arrayData.arrName] != undefined) {
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}
} else {
obj = obj[comp]
}
}
return obj;
}
There are probably more efficient ways to do the Regex, but it's compact.
You can now do stuff like:
var model = {
"m1": {
"Id": "22345",
"People": [
{ "Name": "John", "Numbers": ["07263", "17236", "1223"] },
{ "Name": "Jenny", "Numbers": ["2", "3", "6"] },
{ "Name": "Bob", "Numbers": ["12", "3333", "4444"] }
]
}
}
// Should give you "6"
var x = getDescendantProp(model, "m1.People[1].Numbers[2]");
Performance tests for Andy E's, Jason More's, and my own solution are available at http://jsperf.com/propertyaccessor. Please feel free to run tests using your own browser to add to the data collected.
The prognosis is clear, Andy E's solution is the fastest by far!
For anyone interested, here is the code for my solution to the original question.
function propertyAccessor(object, keys, array) {
/*
Retrieve an object property with a dot notation string.
#param {Object} object Object to access.
#param {String} keys Property to access using 0 or more dots for notation.
#param {Object} [array] Optional array of non-dot notation strings to use instead of keys.
#return {*}
*/
array = array || keys.split('.')
if (array.length > 1) {
// recurse by calling self
return propertyAccessor(object[array.shift()], null, array)
} else {
return object[array]
}
}
Short answer: No, there is no native .access function like you want it. As you correctly mentioned, you would have to define your own function which splits the string and loops/checks over its parts.
Of course, what you always can do (even if its considered bad practice) is to use eval().
Like
var s = 'b.b2';
eval('r.' + s); // 99
Here is a a little better way then #andy's answer, where the obj (context) is optional, it falls back to window if not provided..
function getDescendantProp(desc, obj) {
obj = obj || window;
var arr = desc.split(".");
while (arr.length && (obj = obj[arr.shift()]));
return obj;
};

How to access sub-level object data through function? [duplicate]

This question already has answers here:
Accessing nested JavaScript objects and arrays by string path
(44 answers)
Closed 7 years ago.
I'm temporarily stuck with what appears to be a very simple JavaScript problem, but maybe I'm just missing the right search keywords!
Say we have an object
var r = { a:1, b: {b1:11, b2: 99}};
There are several ways to access the 99:
r.b.b2
r['b']['b2']
What I want is to be able to define a string
var s = "b.b2";
and then access the 99 using
r.s or r[s] //(which of course won't work)
One way is to write a function for it that splits the string on dot and maybe recursively/iteratively gets the property. But is there any simpler/more efficient way? Anything useful in any of the jQuery APIs here?
Here's a naive function I wrote a while ago, but it works for basic object properties:
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
while(arr.length && (obj = obj[arr.shift()]));
return obj;
}
console.log(getDescendantProp(r, "b.b2"));
//-> 99
Although there are answers that extend this to "allow" array index access, that's not really necessary as you can just specify numerical indexes using dot notation with this method:
getDescendantProp({ a: [ 1, 2, 3 ] }, 'a.2');
//-> 3
split and reduce while passing the object as the initalValue
Update
(thanks to comment posted by TeChn4K)
With ES6 syntax, it is even shorter
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var value = s.split('.').reduce((a, b) => a[b], r);
console.log(value);
Old version
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var value = s.split('.').reduce(function(a, b) {
return a[b];
}, r);
console.log(value);
You can use lodash get() and set() methods.
Getting
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// → 3
Setting
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// → 4
If it's possible in your scenario that you could put the entire array variable you're after into a string you could use the eval() function.
var r = { a:1, b: {b1:11, b2: 99}};
var s = "r.b.b2";
alert(eval(s)); // 99
I can feel people reeling in horror
Extending #JohnB's answer, I added a setter value as well. Check out the plunkr at
http://plnkr.co/edit/lo0thC?p=preview
function getSetDescendantProp(obj, desc, value) {
var arr = desc ? desc.split(".") : [];
while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
// handle arrays
if ((match !== null) && (match.length == 3)) {
var arrayData = {
arrName: match[1],
arrIndex: match[2]
};
if (obj[arrayData.arrName] !== undefined) {
if (typeof value !== 'undefined' && arr.length === 0) {
obj[arrayData.arrName][arrayData.arrIndex] = value;
}
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}
continue;
}
// handle regular things
if (typeof value !== 'undefined') {
if (obj[comp] === undefined) {
obj[comp] = {};
}
if (arr.length === 0) {
obj[comp] = value;
}
}
obj = obj[comp];
}
return obj;
}
This is the simplest i could do:
var accessProperties = function(object, string){
var explodedString = string.split('.');
for (i = 0, l = explodedString.length; i<l; i++){
object = object[explodedString[i]];
}
return object;
}
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var o = accessProperties(r, s);
alert(o);//99
you could also do
var s = "['b'].b2";
var num = eval('r'+s);
Here is an extension of Andy E's code, that recurses into arrays and returns all values:
function GetDescendantProps(target, pathString) {
var arr = pathString.split(".");
while(arr.length && (target = target[arr.shift()])){
if (arr.length && target.length && target.forEach) { // handle arrays
var remainder = arr.join('.');
var results = [];
for (var i = 0; i < target.length; i++){
var x = this.GetDescendantProps(target[i], remainder);
if (x) results = results.concat(x);
}
return results;
}
}
return (target) ? [target] : undefined; //single result, wrap in array for consistency
}
So given this target:
var t =
{a:
{b: [
{'c':'x'},
{'not me':'y'},
{'c':'z'}
]
}
};
We get:
GetDescendantProps(t, "a.b.c") === ["x", "z"]; // true
I don't know a supported jQuery API function but I have this function:
var ret = data; // Your object
var childexpr = "b.b2"; // Your expression
if (childexpr != '') {
var childs = childexpr.split('.');
var i;
for (i = 0; i < childs.length && ret != undefined; i++) {
ret = ret[childs[i]];
}
}
return ret;
I've extended Andy E's answer, so that it can also handle arrays:
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
//while (arr.length && (obj = obj[arr.shift()]));
while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
if ((match !== null) && (match.length == 3)) {
var arrayData = { arrName: match[1], arrIndex: match[2] };
if (obj[arrayData.arrName] != undefined) {
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}
} else {
obj = obj[comp]
}
}
return obj;
}
There are probably more efficient ways to do the Regex, but it's compact.
You can now do stuff like:
var model = {
"m1": {
"Id": "22345",
"People": [
{ "Name": "John", "Numbers": ["07263", "17236", "1223"] },
{ "Name": "Jenny", "Numbers": ["2", "3", "6"] },
{ "Name": "Bob", "Numbers": ["12", "3333", "4444"] }
]
}
}
// Should give you "6"
var x = getDescendantProp(model, "m1.People[1].Numbers[2]");
Performance tests for Andy E's, Jason More's, and my own solution are available at http://jsperf.com/propertyaccessor. Please feel free to run tests using your own browser to add to the data collected.
The prognosis is clear, Andy E's solution is the fastest by far!
For anyone interested, here is the code for my solution to the original question.
function propertyAccessor(object, keys, array) {
/*
Retrieve an object property with a dot notation string.
#param {Object} object Object to access.
#param {String} keys Property to access using 0 or more dots for notation.
#param {Object} [array] Optional array of non-dot notation strings to use instead of keys.
#return {*}
*/
array = array || keys.split('.')
if (array.length > 1) {
// recurse by calling self
return propertyAccessor(object[array.shift()], null, array)
} else {
return object[array]
}
}
Short answer: No, there is no native .access function like you want it. As you correctly mentioned, you would have to define your own function which splits the string and loops/checks over its parts.
Of course, what you always can do (even if its considered bad practice) is to use eval().
Like
var s = 'b.b2';
eval('r.' + s); // 99
Here is a a little better way then #andy's answer, where the obj (context) is optional, it falls back to window if not provided..
function getDescendantProp(desc, obj) {
obj = obj || window;
var arr = desc.split(".");
while (arr.length && (obj = obj[arr.shift()]));
return obj;
};

creating deep object property with string [duplicate]

This question already has answers here:
Accessing nested JavaScript objects and arrays by string path
(44 answers)
Closed 7 years ago.
I'm temporarily stuck with what appears to be a very simple JavaScript problem, but maybe I'm just missing the right search keywords!
Say we have an object
var r = { a:1, b: {b1:11, b2: 99}};
There are several ways to access the 99:
r.b.b2
r['b']['b2']
What I want is to be able to define a string
var s = "b.b2";
and then access the 99 using
r.s or r[s] //(which of course won't work)
One way is to write a function for it that splits the string on dot and maybe recursively/iteratively gets the property. But is there any simpler/more efficient way? Anything useful in any of the jQuery APIs here?
Here's a naive function I wrote a while ago, but it works for basic object properties:
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
while(arr.length && (obj = obj[arr.shift()]));
return obj;
}
console.log(getDescendantProp(r, "b.b2"));
//-> 99
Although there are answers that extend this to "allow" array index access, that's not really necessary as you can just specify numerical indexes using dot notation with this method:
getDescendantProp({ a: [ 1, 2, 3 ] }, 'a.2');
//-> 3
split and reduce while passing the object as the initalValue
Update
(thanks to comment posted by TeChn4K)
With ES6 syntax, it is even shorter
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var value = s.split('.').reduce((a, b) => a[b], r);
console.log(value);
Old version
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var value = s.split('.').reduce(function(a, b) {
return a[b];
}, r);
console.log(value);
You can use lodash get() and set() methods.
Getting
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// → 3
Setting
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// → 4
If it's possible in your scenario that you could put the entire array variable you're after into a string you could use the eval() function.
var r = { a:1, b: {b1:11, b2: 99}};
var s = "r.b.b2";
alert(eval(s)); // 99
I can feel people reeling in horror
Extending #JohnB's answer, I added a setter value as well. Check out the plunkr at
http://plnkr.co/edit/lo0thC?p=preview
function getSetDescendantProp(obj, desc, value) {
var arr = desc ? desc.split(".") : [];
while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
// handle arrays
if ((match !== null) && (match.length == 3)) {
var arrayData = {
arrName: match[1],
arrIndex: match[2]
};
if (obj[arrayData.arrName] !== undefined) {
if (typeof value !== 'undefined' && arr.length === 0) {
obj[arrayData.arrName][arrayData.arrIndex] = value;
}
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}
continue;
}
// handle regular things
if (typeof value !== 'undefined') {
if (obj[comp] === undefined) {
obj[comp] = {};
}
if (arr.length === 0) {
obj[comp] = value;
}
}
obj = obj[comp];
}
return obj;
}
This is the simplest i could do:
var accessProperties = function(object, string){
var explodedString = string.split('.');
for (i = 0, l = explodedString.length; i<l; i++){
object = object[explodedString[i]];
}
return object;
}
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var o = accessProperties(r, s);
alert(o);//99
you could also do
var s = "['b'].b2";
var num = eval('r'+s);
Here is an extension of Andy E's code, that recurses into arrays and returns all values:
function GetDescendantProps(target, pathString) {
var arr = pathString.split(".");
while(arr.length && (target = target[arr.shift()])){
if (arr.length && target.length && target.forEach) { // handle arrays
var remainder = arr.join('.');
var results = [];
for (var i = 0; i < target.length; i++){
var x = this.GetDescendantProps(target[i], remainder);
if (x) results = results.concat(x);
}
return results;
}
}
return (target) ? [target] : undefined; //single result, wrap in array for consistency
}
So given this target:
var t =
{a:
{b: [
{'c':'x'},
{'not me':'y'},
{'c':'z'}
]
}
};
We get:
GetDescendantProps(t, "a.b.c") === ["x", "z"]; // true
I don't know a supported jQuery API function but I have this function:
var ret = data; // Your object
var childexpr = "b.b2"; // Your expression
if (childexpr != '') {
var childs = childexpr.split('.');
var i;
for (i = 0; i < childs.length && ret != undefined; i++) {
ret = ret[childs[i]];
}
}
return ret;
I've extended Andy E's answer, so that it can also handle arrays:
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
//while (arr.length && (obj = obj[arr.shift()]));
while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
if ((match !== null) && (match.length == 3)) {
var arrayData = { arrName: match[1], arrIndex: match[2] };
if (obj[arrayData.arrName] != undefined) {
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}
} else {
obj = obj[comp]
}
}
return obj;
}
There are probably more efficient ways to do the Regex, but it's compact.
You can now do stuff like:
var model = {
"m1": {
"Id": "22345",
"People": [
{ "Name": "John", "Numbers": ["07263", "17236", "1223"] },
{ "Name": "Jenny", "Numbers": ["2", "3", "6"] },
{ "Name": "Bob", "Numbers": ["12", "3333", "4444"] }
]
}
}
// Should give you "6"
var x = getDescendantProp(model, "m1.People[1].Numbers[2]");
Performance tests for Andy E's, Jason More's, and my own solution are available at http://jsperf.com/propertyaccessor. Please feel free to run tests using your own browser to add to the data collected.
The prognosis is clear, Andy E's solution is the fastest by far!
For anyone interested, here is the code for my solution to the original question.
function propertyAccessor(object, keys, array) {
/*
Retrieve an object property with a dot notation string.
#param {Object} object Object to access.
#param {String} keys Property to access using 0 or more dots for notation.
#param {Object} [array] Optional array of non-dot notation strings to use instead of keys.
#return {*}
*/
array = array || keys.split('.')
if (array.length > 1) {
// recurse by calling self
return propertyAccessor(object[array.shift()], null, array)
} else {
return object[array]
}
}
Short answer: No, there is no native .access function like you want it. As you correctly mentioned, you would have to define your own function which splits the string and loops/checks over its parts.
Of course, what you always can do (even if its considered bad practice) is to use eval().
Like
var s = 'b.b2';
eval('r.' + s); // 99
Here is a a little better way then #andy's answer, where the obj (context) is optional, it falls back to window if not provided..
function getDescendantProp(desc, obj) {
obj = obj || window;
var arr = desc.split(".");
while (arr.length && (obj = obj[arr.shift()]));
return obj;
};

Object Values as Another Object's Keys [duplicate]

This question already has answers here:
Accessing nested JavaScript objects and arrays by string path
(44 answers)
Closed 7 years ago.
I'm temporarily stuck with what appears to be a very simple JavaScript problem, but maybe I'm just missing the right search keywords!
Say we have an object
var r = { a:1, b: {b1:11, b2: 99}};
There are several ways to access the 99:
r.b.b2
r['b']['b2']
What I want is to be able to define a string
var s = "b.b2";
and then access the 99 using
r.s or r[s] //(which of course won't work)
One way is to write a function for it that splits the string on dot and maybe recursively/iteratively gets the property. But is there any simpler/more efficient way? Anything useful in any of the jQuery APIs here?
Here's a naive function I wrote a while ago, but it works for basic object properties:
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
while(arr.length && (obj = obj[arr.shift()]));
return obj;
}
console.log(getDescendantProp(r, "b.b2"));
//-> 99
Although there are answers that extend this to "allow" array index access, that's not really necessary as you can just specify numerical indexes using dot notation with this method:
getDescendantProp({ a: [ 1, 2, 3 ] }, 'a.2');
//-> 3
split and reduce while passing the object as the initalValue
Update
(thanks to comment posted by TeChn4K)
With ES6 syntax, it is even shorter
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var value = s.split('.').reduce((a, b) => a[b], r);
console.log(value);
Old version
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var value = s.split('.').reduce(function(a, b) {
return a[b];
}, r);
console.log(value);
You can use lodash get() and set() methods.
Getting
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// → 3
Setting
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// → 4
If it's possible in your scenario that you could put the entire array variable you're after into a string you could use the eval() function.
var r = { a:1, b: {b1:11, b2: 99}};
var s = "r.b.b2";
alert(eval(s)); // 99
I can feel people reeling in horror
Extending #JohnB's answer, I added a setter value as well. Check out the plunkr at
http://plnkr.co/edit/lo0thC?p=preview
function getSetDescendantProp(obj, desc, value) {
var arr = desc ? desc.split(".") : [];
while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
// handle arrays
if ((match !== null) && (match.length == 3)) {
var arrayData = {
arrName: match[1],
arrIndex: match[2]
};
if (obj[arrayData.arrName] !== undefined) {
if (typeof value !== 'undefined' && arr.length === 0) {
obj[arrayData.arrName][arrayData.arrIndex] = value;
}
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}
continue;
}
// handle regular things
if (typeof value !== 'undefined') {
if (obj[comp] === undefined) {
obj[comp] = {};
}
if (arr.length === 0) {
obj[comp] = value;
}
}
obj = obj[comp];
}
return obj;
}
This is the simplest i could do:
var accessProperties = function(object, string){
var explodedString = string.split('.');
for (i = 0, l = explodedString.length; i<l; i++){
object = object[explodedString[i]];
}
return object;
}
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var o = accessProperties(r, s);
alert(o);//99
you could also do
var s = "['b'].b2";
var num = eval('r'+s);
Here is an extension of Andy E's code, that recurses into arrays and returns all values:
function GetDescendantProps(target, pathString) {
var arr = pathString.split(".");
while(arr.length && (target = target[arr.shift()])){
if (arr.length && target.length && target.forEach) { // handle arrays
var remainder = arr.join('.');
var results = [];
for (var i = 0; i < target.length; i++){
var x = this.GetDescendantProps(target[i], remainder);
if (x) results = results.concat(x);
}
return results;
}
}
return (target) ? [target] : undefined; //single result, wrap in array for consistency
}
So given this target:
var t =
{a:
{b: [
{'c':'x'},
{'not me':'y'},
{'c':'z'}
]
}
};
We get:
GetDescendantProps(t, "a.b.c") === ["x", "z"]; // true
I don't know a supported jQuery API function but I have this function:
var ret = data; // Your object
var childexpr = "b.b2"; // Your expression
if (childexpr != '') {
var childs = childexpr.split('.');
var i;
for (i = 0; i < childs.length && ret != undefined; i++) {
ret = ret[childs[i]];
}
}
return ret;
I've extended Andy E's answer, so that it can also handle arrays:
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
//while (arr.length && (obj = obj[arr.shift()]));
while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
if ((match !== null) && (match.length == 3)) {
var arrayData = { arrName: match[1], arrIndex: match[2] };
if (obj[arrayData.arrName] != undefined) {
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}
} else {
obj = obj[comp]
}
}
return obj;
}
There are probably more efficient ways to do the Regex, but it's compact.
You can now do stuff like:
var model = {
"m1": {
"Id": "22345",
"People": [
{ "Name": "John", "Numbers": ["07263", "17236", "1223"] },
{ "Name": "Jenny", "Numbers": ["2", "3", "6"] },
{ "Name": "Bob", "Numbers": ["12", "3333", "4444"] }
]
}
}
// Should give you "6"
var x = getDescendantProp(model, "m1.People[1].Numbers[2]");
Performance tests for Andy E's, Jason More's, and my own solution are available at http://jsperf.com/propertyaccessor. Please feel free to run tests using your own browser to add to the data collected.
The prognosis is clear, Andy E's solution is the fastest by far!
For anyone interested, here is the code for my solution to the original question.
function propertyAccessor(object, keys, array) {
/*
Retrieve an object property with a dot notation string.
#param {Object} object Object to access.
#param {String} keys Property to access using 0 or more dots for notation.
#param {Object} [array] Optional array of non-dot notation strings to use instead of keys.
#return {*}
*/
array = array || keys.split('.')
if (array.length > 1) {
// recurse by calling self
return propertyAccessor(object[array.shift()], null, array)
} else {
return object[array]
}
}
Short answer: No, there is no native .access function like you want it. As you correctly mentioned, you would have to define your own function which splits the string and loops/checks over its parts.
Of course, what you always can do (even if its considered bad practice) is to use eval().
Like
var s = 'b.b2';
eval('r.' + s); // 99
Here is a a little better way then #andy's answer, where the obj (context) is optional, it falls back to window if not provided..
function getDescendantProp(desc, obj) {
obj = obj || window;
var arr = desc.split(".");
while (arr.length && (obj = obj[arr.shift()]));
return obj;
};

Accessing sub-object property with a plain string [duplicate]

This question already has answers here:
Accessing nested JavaScript objects and arrays by string path
(44 answers)
Closed 7 years ago.
I'm temporarily stuck with what appears to be a very simple JavaScript problem, but maybe I'm just missing the right search keywords!
Say we have an object
var r = { a:1, b: {b1:11, b2: 99}};
There are several ways to access the 99:
r.b.b2
r['b']['b2']
What I want is to be able to define a string
var s = "b.b2";
and then access the 99 using
r.s or r[s] //(which of course won't work)
One way is to write a function for it that splits the string on dot and maybe recursively/iteratively gets the property. But is there any simpler/more efficient way? Anything useful in any of the jQuery APIs here?
Here's a naive function I wrote a while ago, but it works for basic object properties:
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
while(arr.length && (obj = obj[arr.shift()]));
return obj;
}
console.log(getDescendantProp(r, "b.b2"));
//-> 99
Although there are answers that extend this to "allow" array index access, that's not really necessary as you can just specify numerical indexes using dot notation with this method:
getDescendantProp({ a: [ 1, 2, 3 ] }, 'a.2');
//-> 3
split and reduce while passing the object as the initalValue
Update
(thanks to comment posted by TeChn4K)
With ES6 syntax, it is even shorter
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var value = s.split('.').reduce((a, b) => a[b], r);
console.log(value);
Old version
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var value = s.split('.').reduce(function(a, b) {
return a[b];
}, r);
console.log(value);
You can use lodash get() and set() methods.
Getting
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// → 3
Setting
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// → 4
If it's possible in your scenario that you could put the entire array variable you're after into a string you could use the eval() function.
var r = { a:1, b: {b1:11, b2: 99}};
var s = "r.b.b2";
alert(eval(s)); // 99
I can feel people reeling in horror
Extending #JohnB's answer, I added a setter value as well. Check out the plunkr at
http://plnkr.co/edit/lo0thC?p=preview
function getSetDescendantProp(obj, desc, value) {
var arr = desc ? desc.split(".") : [];
while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
// handle arrays
if ((match !== null) && (match.length == 3)) {
var arrayData = {
arrName: match[1],
arrIndex: match[2]
};
if (obj[arrayData.arrName] !== undefined) {
if (typeof value !== 'undefined' && arr.length === 0) {
obj[arrayData.arrName][arrayData.arrIndex] = value;
}
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}
continue;
}
// handle regular things
if (typeof value !== 'undefined') {
if (obj[comp] === undefined) {
obj[comp] = {};
}
if (arr.length === 0) {
obj[comp] = value;
}
}
obj = obj[comp];
}
return obj;
}
This is the simplest i could do:
var accessProperties = function(object, string){
var explodedString = string.split('.');
for (i = 0, l = explodedString.length; i<l; i++){
object = object[explodedString[i]];
}
return object;
}
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var o = accessProperties(r, s);
alert(o);//99
you could also do
var s = "['b'].b2";
var num = eval('r'+s);
Here is an extension of Andy E's code, that recurses into arrays and returns all values:
function GetDescendantProps(target, pathString) {
var arr = pathString.split(".");
while(arr.length && (target = target[arr.shift()])){
if (arr.length && target.length && target.forEach) { // handle arrays
var remainder = arr.join('.');
var results = [];
for (var i = 0; i < target.length; i++){
var x = this.GetDescendantProps(target[i], remainder);
if (x) results = results.concat(x);
}
return results;
}
}
return (target) ? [target] : undefined; //single result, wrap in array for consistency
}
So given this target:
var t =
{a:
{b: [
{'c':'x'},
{'not me':'y'},
{'c':'z'}
]
}
};
We get:
GetDescendantProps(t, "a.b.c") === ["x", "z"]; // true
I don't know a supported jQuery API function but I have this function:
var ret = data; // Your object
var childexpr = "b.b2"; // Your expression
if (childexpr != '') {
var childs = childexpr.split('.');
var i;
for (i = 0; i < childs.length && ret != undefined; i++) {
ret = ret[childs[i]];
}
}
return ret;
I've extended Andy E's answer, so that it can also handle arrays:
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
//while (arr.length && (obj = obj[arr.shift()]));
while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
if ((match !== null) && (match.length == 3)) {
var arrayData = { arrName: match[1], arrIndex: match[2] };
if (obj[arrayData.arrName] != undefined) {
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}
} else {
obj = obj[comp]
}
}
return obj;
}
There are probably more efficient ways to do the Regex, but it's compact.
You can now do stuff like:
var model = {
"m1": {
"Id": "22345",
"People": [
{ "Name": "John", "Numbers": ["07263", "17236", "1223"] },
{ "Name": "Jenny", "Numbers": ["2", "3", "6"] },
{ "Name": "Bob", "Numbers": ["12", "3333", "4444"] }
]
}
}
// Should give you "6"
var x = getDescendantProp(model, "m1.People[1].Numbers[2]");
Performance tests for Andy E's, Jason More's, and my own solution are available at http://jsperf.com/propertyaccessor. Please feel free to run tests using your own browser to add to the data collected.
The prognosis is clear, Andy E's solution is the fastest by far!
For anyone interested, here is the code for my solution to the original question.
function propertyAccessor(object, keys, array) {
/*
Retrieve an object property with a dot notation string.
#param {Object} object Object to access.
#param {String} keys Property to access using 0 or more dots for notation.
#param {Object} [array] Optional array of non-dot notation strings to use instead of keys.
#return {*}
*/
array = array || keys.split('.')
if (array.length > 1) {
// recurse by calling self
return propertyAccessor(object[array.shift()], null, array)
} else {
return object[array]
}
}
Short answer: No, there is no native .access function like you want it. As you correctly mentioned, you would have to define your own function which splits the string and loops/checks over its parts.
Of course, what you always can do (even if its considered bad practice) is to use eval().
Like
var s = 'b.b2';
eval('r.' + s); // 99
Here is a a little better way then #andy's answer, where the obj (context) is optional, it falls back to window if not provided..
function getDescendantProp(desc, obj) {
obj = obj || window;
var arr = desc.split(".");
while (arr.length && (obj = obj[arr.shift()]));
return obj;
};

Categories

Resources