Javascript optional chaining on dynamic path [duplicate] - javascript

I have a data structure like this :
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'
}
]
};
And I would like to access the data using these variable :
var part1name = "part1.name";
var part2quantity = "part2.qty";
var part3name1 = "part3[0].name";
part1name should be filled with someObject.part1.name 's value, which is "Part 1". Same thing with part2quantity which filled with 60.
Is there anyway to achieve this with either pure javascript or JQuery?

I just made this based on some similar code I already had, it appears to work:
Object.byString = function(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;
}
Usage::
Object.byString(someObj, 'part3[0].name');
See a working demo at http://jsfiddle.net/alnitak/hEsys/
EDIT some have noticed that this code will throw an error if passed a string where the left-most indexes don't correspond to a correctly nested entry within the object. This is a valid concern, but IMHO best addressed with a try / catch block when calling, rather than having this function silently return undefined for an invalid index.

This is now supported by lodash using _.get(obj, property). See https://lodash.com/docs#get
Example from the docs:
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// → 3
_.get(object, ['a', '0', 'b', 'c']);
// → 3
_.get(object, 'a.b.c', 'default');
// → 'default'

This is the solution I use:
function resolve(path, obj=self, separator='.') {
var properties = Array.isArray(path) ? path : path.split(separator)
return properties.reduce((prev, curr) => prev?.[curr], obj)
}
Example usage:
// accessing property path on global scope
resolve("document.body.style.width")
// or
resolve("style.width", document.body)
// accessing array indexes
// (someObject has been defined in the question)
resolve("part3.0.size", someObject) // returns '10'
// accessing non-existent properties
// returns undefined when intermediate properties are not defined:
resolve('properties.that.do.not.exist', {hello:'world'})
// accessing properties with unusual keys by changing the separator
var obj = { object: { 'a.property.name.with.periods': 42 } }
resolve('object->a.property.name.with.periods', obj, '->') // returns 42
// accessing properties with unusual keys by passing a property name array
resolve(['object', 'a.property.name.with.periods'], obj) // returns 42
Limitations:
Can't use brackets ([]) for array indices—though specifying array indices between the separator token (e.g., .) works fine as shown above.

ES6: Only one line in Vanila JS (it return null if don't find instead of giving error):
'path.string'.split('.').reduce((p,c)=>p&&p[c]||null, MyOBJ)
Or example:
'a.b.c'.split('.').reduce((p,c)=>p&&p[c]||null, {a:{b:{c:1}}})
With Optional chaining operator:
'a.b.c'.split('.').reduce((p,c)=>p?.[c], {a:{b:{c:1}}})
For a ready to use function that also recognizes false, 0 and negative number and accept default values as parameter:
const resolvePath = (object, path, defaultValue) => path
.split('.')
.reduce((o, p) => o ? o[p] : defaultValue, object)
Example to use:
resolvePath(window,'document.body') => <body>
resolvePath(window,'document.body.xyz') => undefined
resolvePath(window,'document.body.xyz', null) => null
resolvePath(window,'document.body.xyz', 1) => 1
Bonus:
To set a path (Requested by #rob-gordon) you can use:
const setPath = (object, path, value) => path
.split('.')
.reduce((o,p,i) => o[p] = path.split('.').length === ++i ? value : o[p] || {}, object)
Example:
let myVar = {}
setPath(myVar, 'a.b.c', 42) => 42
console.log(myVar) => {a: {b: {c: 42}}}
Access array with []:
const resolvePath = (object, path, defaultValue) => path
.split(/[\.\[\]\'\"]/)
.filter(p => p)
.reduce((o, p) => o ? o[p] : defaultValue, object)
Example:
const myVar = {a:{b:[{c:1}]}}
resolvePath(myVar,'a.b[0].c') => 1
resolvePath(myVar,'a["b"][\'0\'].c') => 1

You'd have to parse the string yourself:
function getProperty(obj, prop) {
var parts = prop.split('.');
if (Array.isArray(parts)) {
var last = parts.pop(),
l = parts.length,
i = 1,
current = parts[0];
while((obj = obj[current]) && i < l) {
current = parts[i];
i++;
}
if(obj) {
return obj[last];
}
} else {
throw 'parts is not valid array';
}
}
This required that you also define array indexes with dot notation:
var part3name1 = "part3.0.name";
It makes the parsing easier.
DEMO

Works for arrays / arrays inside the object also.
Defensive against invalid values.
/**
* Retrieve nested item from object/array
* #param {Object|Array} obj
* #param {String} path dot separated
* #param {*} def default value ( if result undefined )
* #returns {*}
*/
function path(obj, path, def){
var i, len;
for(i = 0,path = path.split('.'), len = path.length; i < len; i++){
if(!obj || typeof obj !== 'object') return def;
obj = obj[path[i]];
}
if(obj === undefined) return def;
return obj;
}
//////////////////////////
// TEST //
//////////////////////////
var arr = [true, {'sp ace': true}, true]
var obj = {
'sp ace': true,
arr: arr,
nested: {'dotted.str.ing': true},
arr3: arr
}
shouldThrow(`path(obj, "arr.0")`);
shouldBeDefined(`path(obj, "arr[0]")`);
shouldBeEqualToNumber(`path(obj, "arr.length")`, 3);
shouldBeTrue(`path(obj, "sp ace")`);
shouldBeEqualToString(`path(obj, "none.existed.prop", "fallback")`, "fallback");
shouldBeTrue(`path(obj, "nested['dotted.str.ing'])`);
<script src="https://cdn.rawgit.com/coderek/e7b30bac7634a50ad8fd/raw/174b6634c8f57aa8aac0716c5b7b2a7098e03584/js-test.js"></script>

This will probably never see the light of day... but here it is anyway.
Replace [] bracket syntax with .
Split on . character
Remove blank strings
Find the path (otherwise undefined)
(For finding a path to an object, use this pathTo solution.)
// "one liner" (ES6)
const deep_value = (obj, path) =>
path
.replace(/\[|\]\.?/g, '.')
.split('.')
.filter(s => s)
.reduce((acc, val) => acc && acc[val], obj);
// ... and that's 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'
}
// ...
],
'pa[rt3' : [
{
'name': 'Part 3A',
'size': '10',
'qty' : '20'
}
// ...
]
};
console.log(deep_value(someObject, "part1.name")); // Part 1
console.log(deep_value(someObject, "part2.qty")); // 60
console.log(deep_value(someObject, "part3[0].name")); // Part 3A
console.log(deep_value(someObject, "part3[0].....name")); // Part 3A - invalid blank paths removed
console.log(deep_value(someObject, "pa[rt3[0].name")); // undefined - name does not support square brackets

using eval:
var part1name = eval("someObject.part1.name");
wrap to return undefined on error
function path(obj, path) {
try {
return eval("obj." + path);
} catch(e) {
return undefined;
}
}
http://jsfiddle.net/shanimal/b3xTw/
Please use common sense and caution when wielding the power of eval. It's a bit like a light saber, if you turn it on there's a 90% chance you'll sever a limb. Its not for everybody.

You can manage to obtain the value of a deep object member with dot notation without any external JavaScript library with the following simple trick:
function objectGet(obj, path) { return new Function('_', 'return _.' + path)(obj); };
In your case to obtain value of part1.name from someObject just do:
objectGet(someObject, 'part1.name');
Here is a simple fiddle demo: https://jsfiddle.net/harishanchu/oq5esowf/

It's a one liner with lodash.
const deep = { l1: { l2: { l3: "Hello" } } };
const prop = "l1.l2.l3";
const val = _.reduce(prop.split('.'), function(result, value) { return result ? result[value] : undefined; }, deep);
// val === "Hello"
Or even better...
const val = _.get(deep, prop);
Or ES6 version w/ reduce...
const val = prop.split('.').reduce((r, val) => { return r ? r[val] : undefined; }, deep);
Plunkr

I think you are asking for this:
var part1name = someObject.part1.name;
var part2quantity = someObject.part2.qty;
var part3name1 = someObject.part3[0].name;
You could be asking for this:
var part1name = someObject["part1"]["name"];
var part2quantity = someObject["part2"]["qty"];
var part3name1 = someObject["part3"][0]["name"];
Both of which will work
Or maybe you are asking for this
var partName = "part1";
var nameStr = "name";
var part1name = someObject[partName][nameStr];
Finally you could be asking for this
var partName = "part1.name";
var partBits = partName.split(".");
var part1name = someObject[partBits[0]][partBits[1]];

Instead of trying to emulate JS syntax which you will have to spend a bunch of compute parsing, or just get wrong/forget things like a bunch of these answers (keys with .s in, anyone?), just use an array of keys.
var part1name = Object.get(someObject, ['part1', 'name']);
var part2quantity = Object.get(someObject, ['part2', 'qty']);
var part3name1 = Object.get(someObject, ['part3', 0, 'name']);
If you need to use a single string instead, simply JSONify it.
Another improvement in this method is that you can delete/set the root level object.
function resolve(obj, path) {
let root = obj = [obj];
path = [0, ...path];
while (path.length > 1)
obj = obj[path.shift()];
return [obj, path[0], root];
}
Object.get = (obj, path) => {
let [parent, key] = resolve(obj, path);
return parent[key];
};
Object.del = (obj, path) => {
let [parent, key, root] = resolve(obj, path);
delete parent[key];
return root[0];
};
Object.set = (obj, path, value) => {
let [parent, key, root] = resolve(obj, path);
parent[key] = value;
return root[0];
};
Demo of other features:
The bob = for .set(/.del( isn't necessary unless your path might be empty (manipulating the root object).
I prove that I don't clone the object by using steve to keep a reference to the original and checking bob == steve //true after that first .set(

Just in case, anyone's visiting this question in 2017 or later and looking for an easy-to-remember way, here's an elaborate blog post on Accessing Nested Objects in JavaScript without being bamboozled by
Cannot read property 'foo' of undefined error
Access Nested Objects Using Array Reduce
Let's take this example structure
const user = {
id: 101,
email: 'jack#dev.com',
personalInfo: {
name: 'Jack',
address: [{
line1: 'westwish st',
line2: 'washmasher',
city: 'wallas',
state: 'WX'
}]
}
}
To be able to access nested arrays, you can write your own array reduce util.
const getNestedObject = (nestedObj, pathArr) => {
return pathArr.reduce((obj, key) =>
(obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);
}
// pass in your object structure as array elements
const name = getNestedObject(user, ['personalInfo', 'name']);
// to access nested array, just pass in array index as an element the path array.
const city = getNestedObject(user, ['personalInfo', 'address', 0, 'city']);
// this will return the city from the first address item.
There is also an excellent type handling minimal library typy that does all this for you.
With typy, your code will look like this
const city = t(user, 'personalInfo.address[0].city').safeObject;
Disclaimer: I am the author of this package.

Here I offer more ways, which seem faster in many respects:
Option 1: Split string on . or [ or ] or ' or ", reverse it, skip empty items.
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var parts = path.split(/\[|\]|\.|'|"/g).reverse(), name; // (why reverse? because it's usually faster to pop off the end of an array)
while (parts.length) { name=parts.pop(); if (name) origin=origin[name]; }
return origin;
}
Option 2 (fastest of all, except eval): Low level character scan (no regex/split/etc, just a quick char scan).
Note: This one does not support quotes for indexes.
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var c = '', pc, i = 0, n = path.length, name = '';
if (n) while (i<=n) ((c = path[i++]) == '.' || c == '[' || c == ']' || c == void 0) ? (name?(origin = origin[name], name = ''):(pc=='.'||pc=='['||pc==']'&&c==']'?i=n+2:void 0),pc=c) : name += c;
if (i==n+2) throw "Invalid path: "+path;
return origin;
} // (around 1,000,000+/- ops/sec)
Option 3: (new: option 2 expanded to support quotes - a bit slower, but still fast)
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var c, pc, i = 0, n = path.length, name = '', q;
while (i<=n)
((c = path[i++]) == '.' || c == '[' || c == ']' || c == "'" || c == '"' || c == void 0) ? (c==q&&path[i]==']'?q='':q?name+=c:name?(origin?origin=origin[name]:i=n+2,name='') : (pc=='['&&(c=='"'||c=="'")?q=c:pc=='.'||pc=='['||pc==']'&&c==']'||pc=='"'||pc=="'"?i=n+2:void 0), pc=c) : name += c;
if (i==n+2 || name) throw "Invalid path: "+path;
return origin;
}
JSPerf: http://jsperf.com/ways-to-dereference-a-delimited-property-string/3
"eval(...)" is still king though (performance wise that is). If you have property paths directly under your control, there shouldn't be any issues with using 'eval' (especially if speed is desired). If pulling property paths "over the wire" (on the line!? lol :P), then yes, use something else to be safe. Only an idiot would say to never use "eval" at all, as there ARE good reasons when to use it. Also, "It is used in Doug Crockford's JSON parser." If the input is safe, then no problems at all. Use the right tool for the right job, that's it.

AngularJS
Speigg's approach is very neat and clean, though I found this reply while searching for the solution of accessing AngularJS $scope properties by string path and with a little modification it does the job:
$scope.resolve = function( path, obj ) {
return path.split('.').reduce( function( prev, curr ) {
return prev[curr];
}, obj || this );
}
Just place this function in your root controller and use it any child scope like this:
$scope.resolve( 'path.to.any.object.in.scope')

If you want a solution that can properly detect and report details of any issue with the path parsing, I wrote my own solution to this - library path-value.
const {resolveValue} = require('path-value');
resolveValue(someObject, 'part1.name'); //=> Part 1
resolveValue(someObject, 'part2.qty'); //=> 50
resolveValue(someObject, 'part3.0.name'); //=> Part 3A
Note that for indexes we use .0, and not [0], because parsing the latter adds a performance penalty, while .0 works directly in JavaScript, and is thus very fast.
However, full ES5 JavaScript syntax is also supported, it just needs to be tokenized first:
const {resolveValue, tokenizePath} = require('path-value');
const path = tokenizePath('part3[0].name'); //=> ['part3', '0', 'name']
resolveValue(someObject, path); //=> Part 3A

/**
* Access a deep value inside a object
* Works by passing a path like "foo.bar", also works with nested arrays like "foo[0][1].baz"
* #author Victor B. https://gist.github.com/victornpb/4c7882c1b9d36292308e
* Unit tests: http://jsfiddle.net/Victornpb/0u1qygrh/
*/
function getDeepVal(obj, path) {
if (typeof obj === "undefined" || obj === null) return;
path = path.split(/[\.\[\]\"\']{1,2}/);
for (var i = 0, l = path.length; i < l; i++) {
if (path[i] === "") continue;
obj = obj[path[i]];
if (typeof obj === "undefined" || obj === null) return;
}
return obj;
}
Works with
getDeepVal(obj,'foo.bar')
getDeepVal(obj,'foo.1.bar')
getDeepVal(obj,'foo[0].baz')
getDeepVal(obj,'foo[1][2]')
getDeepVal(obj,"foo['bar'].baz")
getDeepVal(obj,"foo['bar']['baz']")
getDeepVal(obj,"foo.bar.0.baz[1]['2']['w'].aaa[\"f\"].bb")

I haven't yet found a package to do all of the operations with a string path, so I ended up writing my own quick little package which supports insert(), get() (with default return), set() and remove() operations.
You can use dot notation, brackets, number indices, string number properties, and keys with non-word characters. Simple usage below:
> var jsocrud = require('jsocrud');
...
// Get (Read) ---
> var obj = {
> foo: [
> {
> 'key w/ non-word chars': 'bar'
> }
> ]
> };
undefined
> jsocrud.get(obj, '.foo[0]["key w/ non-word chars"]');
'bar'
https://www.npmjs.com/package/jsocrud
https://github.com/vertical-knowledge/jsocrud

Simple function, allowing for either a string or array path.
function get(obj, path) {
if(typeof path === 'string') path = path.split('.');
if(path.length === 0) return obj;
return get(obj[path[0]], path.slice(1));
}
const obj = {a: {b: {c: 'foo'}}};
console.log(get(obj, 'a.b.c')); //foo
OR
console.log(get(obj, ['a', 'b', 'c'])); //foo

There is an npm module now for doing this: https://github.com/erictrinh/safe-access
Example usage:
var access = require('safe-access');
access(very, 'nested.property.and.array[0]');

While reduce is good, I am surprised no one used forEach:
function valueForKeyPath(obj, path){
const keys = path.split('.');
keys.forEach((key)=> obj = obj[key]);
return obj;
};
Test

I'm developing online-shop with React. I tried to change values in copied state object to update original state with it on submit.
Examples above haven't worked for me, because most of them mutate structure of copied object. I found working example of the function for accessing and changing values of the deep nested object properties: https://lowrey.me/create-an-object-by-path-in-javascript-2/ Here it is:
const createPath = (obj, path, value = null) => {
path = typeof path === 'string' ? path.split('.') : path;
let current = obj;
while (path.length > 1) {
const [head, ...tail] = path;
path = tail;
if (current[head] === undefined) {
current[head] = {};
}
current = current[head];
}
current[path[0]] = value;
return obj;
};

You can use ramda library.
Learning ramda also helps you to work with immutable objects easily.
var obj = {
a:{
b: {
c:[100,101,{
d: 1000
}]
}
}
};
var lens = R.lensPath('a.b.c.2.d'.split('.'));
var result = R.view(lens, obj);
https://codepen.io/ghominejad/pen/BayJZOQ

Based on Alnitak's answer.
I wrapped the polyfill in a check, and reduced the function to a single chained reduction.
if (Object.byPath === undefined) {
Object.byPath = (obj, path) => path
.replace(/\[(\w+)\]/g, '.$1')
.replace(/^\./, '')
.split(/\./g)
.reduce((ref, key) => key in ref ? ref[key] : ref, obj)
}
const data = {
foo: {
bar: [{
baz: 1
}]
}
}
console.log(Object.byPath(data, 'foo.bar[0].baz'))

This can be simplified by splitting the logic into three separate functions:
const isVal = a => a != null; // everything except undefined + null
const prop = prop => obj => {
if (isVal(obj)) {
const value = obj[prop];
if (isVal(value)) return value;
else return undefined;
} else return undefined;
};
const path = paths => obj => {
const pathList = typeof paths === 'string' ? paths.split('.') : paths;
return pathList.reduce((value, key) => prop(key)(value), obj);
};
//usage:
const myObject = { foo: { bar: { baz: 'taco' } } };
const result = path('foo.bar')(myObject);
//results => { baz: 'taco' }
This variation supports:
passing an array or string argument
dealing with undefined values during invocation and execution
testing each function independently
using each function independently

DotObject = obj => new Proxy(obj, {
get: function(o,k) {
const m = k.match(/(.+?)\.(.+)/)
return m ? this.get(o[m[1]], m[2]) : o[k]
}
})
const test = DotObject({a: {b: {c: 'wow'}}})
console.log(test['a.b.c'])

If you need to access different nested key without knowing it at coding time (it will be trivial to address them) you can use the array notation accessor:
var part1name = someObject['part1']['name'];
var part2quantity = someObject['part2']['qty'];
var part3name1 = someObject['part3'][0]['name'];
They are equivalent to the dot notation accessor and may vary at runtime, for example:
var part = 'part1';
var property = 'name';
var part1name = someObject[part][property];
is equivalent to
var part1name = someObject['part1']['name'];
or
var part1name = someObject.part1.name;
I hope this address your question...
EDIT
I won't use a string to mantain a sort of xpath query to access an object value.
As you have to call a function to parse the query and retrieve the value I would follow another path (not :
var part1name = function(){ return this.part1.name; }
var part2quantity = function() { return this['part2']['qty']; }
var part3name1 = function() { return this.part3[0]['name'];}
// usage: part1name.apply(someObject);
or, if you are uneasy with the apply method
var part1name = function(obj){ return obj.part1.name; }
var part2quantity = function(obj) { return obj['part2']['qty']; }
var part3name1 = function(obj) { return obj.part3[0]['name'];}
// usage: part1name(someObject);
The functions are shorter, clearer, the interpreter check them for you for syntax errors and so on.
By the way, I feel that a simple assignment made at right time will be sufficent...

Just had the same question recently and successfully used https://npmjs.org/package/tea-properties which also set nested object/arrays :
get:
var o = {
prop: {
arr: [
{foo: 'bar'}
]
}
};
var properties = require('tea-properties');
var value = properties.get(o, 'prop.arr[0].foo');
assert(value, 'bar'); // true
set:
var o = {};
var properties = require('tea-properties');
properties.set(o, 'prop.arr[0].foo', 'bar');
assert(o.prop.arr[0].foo, 'bar'); // true

Based on a previous answer, I have created a function that can also handle brackets. But no dots inside them due to the split.
function get(obj, str) {
return str.split(/\.|\[/g).map(function(crumb) {
return crumb.replace(/\]$/, '').trim().replace(/^(["'])((?:(?!\1)[^\\]|\\.)*?)\1$/, (match, quote, str) => str.replace(/\\(\\)?/g, "$1"));
}).reduce(function(obj, prop) {
return obj ? obj[prop] : undefined;
}, obj);
}

// (IE9+) Two steps
var pathString = "[0]['property'].others[3].next['final']";
var obj = [{
property: {
others: [1, 2, 3, {
next: {
final: "SUCCESS"
}
}]
}
}];
// Turn string to path array
var pathArray = pathString
.replace(/\[["']?([\w]+)["']?\]/g,".$1")
.split(".")
.splice(1);
// Add object prototype method
Object.prototype.path = function (path) {
try {
return [this].concat(path).reduce(function (f, l) {
return f[l];
});
} catch (e) {
console.error(e);
}
};
// usage
console.log(obj.path(pathArray));
console.log(obj.path([0,"doesNotExist"]));

Related

How to get sub field of an object javascript [duplicate]

I have a data structure like this :
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'
}
]
};
And I would like to access the data using these variable :
var part1name = "part1.name";
var part2quantity = "part2.qty";
var part3name1 = "part3[0].name";
part1name should be filled with someObject.part1.name 's value, which is "Part 1". Same thing with part2quantity which filled with 60.
Is there anyway to achieve this with either pure javascript or JQuery?
I just made this based on some similar code I already had, it appears to work:
Object.byString = function(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;
}
Usage::
Object.byString(someObj, 'part3[0].name');
See a working demo at http://jsfiddle.net/alnitak/hEsys/
EDIT some have noticed that this code will throw an error if passed a string where the left-most indexes don't correspond to a correctly nested entry within the object. This is a valid concern, but IMHO best addressed with a try / catch block when calling, rather than having this function silently return undefined for an invalid index.
This is now supported by lodash using _.get(obj, property). See https://lodash.com/docs#get
Example from the docs:
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// → 3
_.get(object, ['a', '0', 'b', 'c']);
// → 3
_.get(object, 'a.b.c', 'default');
// → 'default'
This is the solution I use:
function resolve(path, obj=self, separator='.') {
var properties = Array.isArray(path) ? path : path.split(separator)
return properties.reduce((prev, curr) => prev?.[curr], obj)
}
Example usage:
// accessing property path on global scope
resolve("document.body.style.width")
// or
resolve("style.width", document.body)
// accessing array indexes
// (someObject has been defined in the question)
resolve("part3.0.size", someObject) // returns '10'
// accessing non-existent properties
// returns undefined when intermediate properties are not defined:
resolve('properties.that.do.not.exist', {hello:'world'})
// accessing properties with unusual keys by changing the separator
var obj = { object: { 'a.property.name.with.periods': 42 } }
resolve('object->a.property.name.with.periods', obj, '->') // returns 42
// accessing properties with unusual keys by passing a property name array
resolve(['object', 'a.property.name.with.periods'], obj) // returns 42
Limitations:
Can't use brackets ([]) for array indices—though specifying array indices between the separator token (e.g., .) works fine as shown above.
ES6: Only one line in Vanila JS (it return null if don't find instead of giving error):
'path.string'.split('.').reduce((p,c)=>p&&p[c]||null, MyOBJ)
Or example:
'a.b.c'.split('.').reduce((p,c)=>p&&p[c]||null, {a:{b:{c:1}}})
With Optional chaining operator:
'a.b.c'.split('.').reduce((p,c)=>p?.[c], {a:{b:{c:1}}})
For a ready to use function that also recognizes false, 0 and negative number and accept default values as parameter:
const resolvePath = (object, path, defaultValue) => path
.split('.')
.reduce((o, p) => o ? o[p] : defaultValue, object)
Example to use:
resolvePath(window,'document.body') => <body>
resolvePath(window,'document.body.xyz') => undefined
resolvePath(window,'document.body.xyz', null) => null
resolvePath(window,'document.body.xyz', 1) => 1
Bonus:
To set a path (Requested by #rob-gordon) you can use:
const setPath = (object, path, value) => path
.split('.')
.reduce((o,p,i) => o[p] = path.split('.').length === ++i ? value : o[p] || {}, object)
Example:
let myVar = {}
setPath(myVar, 'a.b.c', 42) => 42
console.log(myVar) => {a: {b: {c: 42}}}
Access array with []:
const resolvePath = (object, path, defaultValue) => path
.split(/[\.\[\]\'\"]/)
.filter(p => p)
.reduce((o, p) => o ? o[p] : defaultValue, object)
Example:
const myVar = {a:{b:[{c:1}]}}
resolvePath(myVar,'a.b[0].c') => 1
resolvePath(myVar,'a["b"][\'0\'].c') => 1
You'd have to parse the string yourself:
function getProperty(obj, prop) {
var parts = prop.split('.');
if (Array.isArray(parts)) {
var last = parts.pop(),
l = parts.length,
i = 1,
current = parts[0];
while((obj = obj[current]) && i < l) {
current = parts[i];
i++;
}
if(obj) {
return obj[last];
}
} else {
throw 'parts is not valid array';
}
}
This required that you also define array indexes with dot notation:
var part3name1 = "part3.0.name";
It makes the parsing easier.
DEMO
Works for arrays / arrays inside the object also.
Defensive against invalid values.
/**
* Retrieve nested item from object/array
* #param {Object|Array} obj
* #param {String} path dot separated
* #param {*} def default value ( if result undefined )
* #returns {*}
*/
function path(obj, path, def){
var i, len;
for(i = 0,path = path.split('.'), len = path.length; i < len; i++){
if(!obj || typeof obj !== 'object') return def;
obj = obj[path[i]];
}
if(obj === undefined) return def;
return obj;
}
//////////////////////////
// TEST //
//////////////////////////
var arr = [true, {'sp ace': true}, true]
var obj = {
'sp ace': true,
arr: arr,
nested: {'dotted.str.ing': true},
arr3: arr
}
shouldThrow(`path(obj, "arr.0")`);
shouldBeDefined(`path(obj, "arr[0]")`);
shouldBeEqualToNumber(`path(obj, "arr.length")`, 3);
shouldBeTrue(`path(obj, "sp ace")`);
shouldBeEqualToString(`path(obj, "none.existed.prop", "fallback")`, "fallback");
shouldBeTrue(`path(obj, "nested['dotted.str.ing'])`);
<script src="https://cdn.rawgit.com/coderek/e7b30bac7634a50ad8fd/raw/174b6634c8f57aa8aac0716c5b7b2a7098e03584/js-test.js"></script>
This will probably never see the light of day... but here it is anyway.
Replace [] bracket syntax with .
Split on . character
Remove blank strings
Find the path (otherwise undefined)
(For finding a path to an object, use this pathTo solution.)
// "one liner" (ES6)
const deep_value = (obj, path) =>
path
.replace(/\[|\]\.?/g, '.')
.split('.')
.filter(s => s)
.reduce((acc, val) => acc && acc[val], obj);
// ... and that's 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'
}
// ...
],
'pa[rt3' : [
{
'name': 'Part 3A',
'size': '10',
'qty' : '20'
}
// ...
]
};
console.log(deep_value(someObject, "part1.name")); // Part 1
console.log(deep_value(someObject, "part2.qty")); // 60
console.log(deep_value(someObject, "part3[0].name")); // Part 3A
console.log(deep_value(someObject, "part3[0].....name")); // Part 3A - invalid blank paths removed
console.log(deep_value(someObject, "pa[rt3[0].name")); // undefined - name does not support square brackets
using eval:
var part1name = eval("someObject.part1.name");
wrap to return undefined on error
function path(obj, path) {
try {
return eval("obj." + path);
} catch(e) {
return undefined;
}
}
http://jsfiddle.net/shanimal/b3xTw/
Please use common sense and caution when wielding the power of eval. It's a bit like a light saber, if you turn it on there's a 90% chance you'll sever a limb. Its not for everybody.
You can manage to obtain the value of a deep object member with dot notation without any external JavaScript library with the following simple trick:
function objectGet(obj, path) { return new Function('_', 'return _.' + path)(obj); };
In your case to obtain value of part1.name from someObject just do:
objectGet(someObject, 'part1.name');
Here is a simple fiddle demo: https://jsfiddle.net/harishanchu/oq5esowf/
It's a one liner with lodash.
const deep = { l1: { l2: { l3: "Hello" } } };
const prop = "l1.l2.l3";
const val = _.reduce(prop.split('.'), function(result, value) { return result ? result[value] : undefined; }, deep);
// val === "Hello"
Or even better...
const val = _.get(deep, prop);
Or ES6 version w/ reduce...
const val = prop.split('.').reduce((r, val) => { return r ? r[val] : undefined; }, deep);
Plunkr
I think you are asking for this:
var part1name = someObject.part1.name;
var part2quantity = someObject.part2.qty;
var part3name1 = someObject.part3[0].name;
You could be asking for this:
var part1name = someObject["part1"]["name"];
var part2quantity = someObject["part2"]["qty"];
var part3name1 = someObject["part3"][0]["name"];
Both of which will work
Or maybe you are asking for this
var partName = "part1";
var nameStr = "name";
var part1name = someObject[partName][nameStr];
Finally you could be asking for this
var partName = "part1.name";
var partBits = partName.split(".");
var part1name = someObject[partBits[0]][partBits[1]];
Instead of trying to emulate JS syntax which you will have to spend a bunch of compute parsing, or just get wrong/forget things like a bunch of these answers (keys with .s in, anyone?), just use an array of keys.
var part1name = Object.get(someObject, ['part1', 'name']);
var part2quantity = Object.get(someObject, ['part2', 'qty']);
var part3name1 = Object.get(someObject, ['part3', 0, 'name']);
If you need to use a single string instead, simply JSONify it.
Another improvement in this method is that you can delete/set the root level object.
function resolve(obj, path) {
let root = obj = [obj];
path = [0, ...path];
while (path.length > 1)
obj = obj[path.shift()];
return [obj, path[0], root];
}
Object.get = (obj, path) => {
let [parent, key] = resolve(obj, path);
return parent[key];
};
Object.del = (obj, path) => {
let [parent, key, root] = resolve(obj, path);
delete parent[key];
return root[0];
};
Object.set = (obj, path, value) => {
let [parent, key, root] = resolve(obj, path);
parent[key] = value;
return root[0];
};
Demo of other features:
The bob = for .set(/.del( isn't necessary unless your path might be empty (manipulating the root object).
I prove that I don't clone the object by using steve to keep a reference to the original and checking bob == steve //true after that first .set(
Just in case, anyone's visiting this question in 2017 or later and looking for an easy-to-remember way, here's an elaborate blog post on Accessing Nested Objects in JavaScript without being bamboozled by
Cannot read property 'foo' of undefined error
Access Nested Objects Using Array Reduce
Let's take this example structure
const user = {
id: 101,
email: 'jack#dev.com',
personalInfo: {
name: 'Jack',
address: [{
line1: 'westwish st',
line2: 'washmasher',
city: 'wallas',
state: 'WX'
}]
}
}
To be able to access nested arrays, you can write your own array reduce util.
const getNestedObject = (nestedObj, pathArr) => {
return pathArr.reduce((obj, key) =>
(obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);
}
// pass in your object structure as array elements
const name = getNestedObject(user, ['personalInfo', 'name']);
// to access nested array, just pass in array index as an element the path array.
const city = getNestedObject(user, ['personalInfo', 'address', 0, 'city']);
// this will return the city from the first address item.
There is also an excellent type handling minimal library typy that does all this for you.
With typy, your code will look like this
const city = t(user, 'personalInfo.address[0].city').safeObject;
Disclaimer: I am the author of this package.
Here I offer more ways, which seem faster in many respects:
Option 1: Split string on . or [ or ] or ' or ", reverse it, skip empty items.
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var parts = path.split(/\[|\]|\.|'|"/g).reverse(), name; // (why reverse? because it's usually faster to pop off the end of an array)
while (parts.length) { name=parts.pop(); if (name) origin=origin[name]; }
return origin;
}
Option 2 (fastest of all, except eval): Low level character scan (no regex/split/etc, just a quick char scan).
Note: This one does not support quotes for indexes.
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var c = '', pc, i = 0, n = path.length, name = '';
if (n) while (i<=n) ((c = path[i++]) == '.' || c == '[' || c == ']' || c == void 0) ? (name?(origin = origin[name], name = ''):(pc=='.'||pc=='['||pc==']'&&c==']'?i=n+2:void 0),pc=c) : name += c;
if (i==n+2) throw "Invalid path: "+path;
return origin;
} // (around 1,000,000+/- ops/sec)
Option 3: (new: option 2 expanded to support quotes - a bit slower, but still fast)
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var c, pc, i = 0, n = path.length, name = '', q;
while (i<=n)
((c = path[i++]) == '.' || c == '[' || c == ']' || c == "'" || c == '"' || c == void 0) ? (c==q&&path[i]==']'?q='':q?name+=c:name?(origin?origin=origin[name]:i=n+2,name='') : (pc=='['&&(c=='"'||c=="'")?q=c:pc=='.'||pc=='['||pc==']'&&c==']'||pc=='"'||pc=="'"?i=n+2:void 0), pc=c) : name += c;
if (i==n+2 || name) throw "Invalid path: "+path;
return origin;
}
JSPerf: http://jsperf.com/ways-to-dereference-a-delimited-property-string/3
"eval(...)" is still king though (performance wise that is). If you have property paths directly under your control, there shouldn't be any issues with using 'eval' (especially if speed is desired). If pulling property paths "over the wire" (on the line!? lol :P), then yes, use something else to be safe. Only an idiot would say to never use "eval" at all, as there ARE good reasons when to use it. Also, "It is used in Doug Crockford's JSON parser." If the input is safe, then no problems at all. Use the right tool for the right job, that's it.
AngularJS
Speigg's approach is very neat and clean, though I found this reply while searching for the solution of accessing AngularJS $scope properties by string path and with a little modification it does the job:
$scope.resolve = function( path, obj ) {
return path.split('.').reduce( function( prev, curr ) {
return prev[curr];
}, obj || this );
}
Just place this function in your root controller and use it any child scope like this:
$scope.resolve( 'path.to.any.object.in.scope')
If you want a solution that can properly detect and report details of any issue with the path parsing, I wrote my own solution to this - library path-value.
const {resolveValue} = require('path-value');
resolveValue(someObject, 'part1.name'); //=> Part 1
resolveValue(someObject, 'part2.qty'); //=> 50
resolveValue(someObject, 'part3.0.name'); //=> Part 3A
Note that for indexes we use .0, and not [0], because parsing the latter adds a performance penalty, while .0 works directly in JavaScript, and is thus very fast.
However, full ES5 JavaScript syntax is also supported, it just needs to be tokenized first:
const {resolveValue, tokenizePath} = require('path-value');
const path = tokenizePath('part3[0].name'); //=> ['part3', '0', 'name']
resolveValue(someObject, path); //=> Part 3A
/**
* Access a deep value inside a object
* Works by passing a path like "foo.bar", also works with nested arrays like "foo[0][1].baz"
* #author Victor B. https://gist.github.com/victornpb/4c7882c1b9d36292308e
* Unit tests: http://jsfiddle.net/Victornpb/0u1qygrh/
*/
function getDeepVal(obj, path) {
if (typeof obj === "undefined" || obj === null) return;
path = path.split(/[\.\[\]\"\']{1,2}/);
for (var i = 0, l = path.length; i < l; i++) {
if (path[i] === "") continue;
obj = obj[path[i]];
if (typeof obj === "undefined" || obj === null) return;
}
return obj;
}
Works with
getDeepVal(obj,'foo.bar')
getDeepVal(obj,'foo.1.bar')
getDeepVal(obj,'foo[0].baz')
getDeepVal(obj,'foo[1][2]')
getDeepVal(obj,"foo['bar'].baz")
getDeepVal(obj,"foo['bar']['baz']")
getDeepVal(obj,"foo.bar.0.baz[1]['2']['w'].aaa[\"f\"].bb")
I haven't yet found a package to do all of the operations with a string path, so I ended up writing my own quick little package which supports insert(), get() (with default return), set() and remove() operations.
You can use dot notation, brackets, number indices, string number properties, and keys with non-word characters. Simple usage below:
> var jsocrud = require('jsocrud');
...
// Get (Read) ---
> var obj = {
> foo: [
> {
> 'key w/ non-word chars': 'bar'
> }
> ]
> };
undefined
> jsocrud.get(obj, '.foo[0]["key w/ non-word chars"]');
'bar'
https://www.npmjs.com/package/jsocrud
https://github.com/vertical-knowledge/jsocrud
Simple function, allowing for either a string or array path.
function get(obj, path) {
if(typeof path === 'string') path = path.split('.');
if(path.length === 0) return obj;
return get(obj[path[0]], path.slice(1));
}
const obj = {a: {b: {c: 'foo'}}};
console.log(get(obj, 'a.b.c')); //foo
OR
console.log(get(obj, ['a', 'b', 'c'])); //foo
There is an npm module now for doing this: https://github.com/erictrinh/safe-access
Example usage:
var access = require('safe-access');
access(very, 'nested.property.and.array[0]');
While reduce is good, I am surprised no one used forEach:
function valueForKeyPath(obj, path){
const keys = path.split('.');
keys.forEach((key)=> obj = obj[key]);
return obj;
};
Test
I'm developing online-shop with React. I tried to change values in copied state object to update original state with it on submit.
Examples above haven't worked for me, because most of them mutate structure of copied object. I found working example of the function for accessing and changing values of the deep nested object properties: https://lowrey.me/create-an-object-by-path-in-javascript-2/ Here it is:
const createPath = (obj, path, value = null) => {
path = typeof path === 'string' ? path.split('.') : path;
let current = obj;
while (path.length > 1) {
const [head, ...tail] = path;
path = tail;
if (current[head] === undefined) {
current[head] = {};
}
current = current[head];
}
current[path[0]] = value;
return obj;
};
You can use ramda library.
Learning ramda also helps you to work with immutable objects easily.
var obj = {
a:{
b: {
c:[100,101,{
d: 1000
}]
}
}
};
var lens = R.lensPath('a.b.c.2.d'.split('.'));
var result = R.view(lens, obj);
https://codepen.io/ghominejad/pen/BayJZOQ
Based on Alnitak's answer.
I wrapped the polyfill in a check, and reduced the function to a single chained reduction.
if (Object.byPath === undefined) {
Object.byPath = (obj, path) => path
.replace(/\[(\w+)\]/g, '.$1')
.replace(/^\./, '')
.split(/\./g)
.reduce((ref, key) => key in ref ? ref[key] : ref, obj)
}
const data = {
foo: {
bar: [{
baz: 1
}]
}
}
console.log(Object.byPath(data, 'foo.bar[0].baz'))
This can be simplified by splitting the logic into three separate functions:
const isVal = a => a != null; // everything except undefined + null
const prop = prop => obj => {
if (isVal(obj)) {
const value = obj[prop];
if (isVal(value)) return value;
else return undefined;
} else return undefined;
};
const path = paths => obj => {
const pathList = typeof paths === 'string' ? paths.split('.') : paths;
return pathList.reduce((value, key) => prop(key)(value), obj);
};
//usage:
const myObject = { foo: { bar: { baz: 'taco' } } };
const result = path('foo.bar')(myObject);
//results => { baz: 'taco' }
This variation supports:
passing an array or string argument
dealing with undefined values during invocation and execution
testing each function independently
using each function independently
DotObject = obj => new Proxy(obj, {
get: function(o,k) {
const m = k.match(/(.+?)\.(.+)/)
return m ? this.get(o[m[1]], m[2]) : o[k]
}
})
const test = DotObject({a: {b: {c: 'wow'}}})
console.log(test['a.b.c'])
If you need to access different nested key without knowing it at coding time (it will be trivial to address them) you can use the array notation accessor:
var part1name = someObject['part1']['name'];
var part2quantity = someObject['part2']['qty'];
var part3name1 = someObject['part3'][0]['name'];
They are equivalent to the dot notation accessor and may vary at runtime, for example:
var part = 'part1';
var property = 'name';
var part1name = someObject[part][property];
is equivalent to
var part1name = someObject['part1']['name'];
or
var part1name = someObject.part1.name;
I hope this address your question...
EDIT
I won't use a string to mantain a sort of xpath query to access an object value.
As you have to call a function to parse the query and retrieve the value I would follow another path (not :
var part1name = function(){ return this.part1.name; }
var part2quantity = function() { return this['part2']['qty']; }
var part3name1 = function() { return this.part3[0]['name'];}
// usage: part1name.apply(someObject);
or, if you are uneasy with the apply method
var part1name = function(obj){ return obj.part1.name; }
var part2quantity = function(obj) { return obj['part2']['qty']; }
var part3name1 = function(obj) { return obj.part3[0]['name'];}
// usage: part1name(someObject);
The functions are shorter, clearer, the interpreter check them for you for syntax errors and so on.
By the way, I feel that a simple assignment made at right time will be sufficent...
Just had the same question recently and successfully used https://npmjs.org/package/tea-properties which also set nested object/arrays :
get:
var o = {
prop: {
arr: [
{foo: 'bar'}
]
}
};
var properties = require('tea-properties');
var value = properties.get(o, 'prop.arr[0].foo');
assert(value, 'bar'); // true
set:
var o = {};
var properties = require('tea-properties');
properties.set(o, 'prop.arr[0].foo', 'bar');
assert(o.prop.arr[0].foo, 'bar'); // true
Based on a previous answer, I have created a function that can also handle brackets. But no dots inside them due to the split.
function get(obj, str) {
return str.split(/\.|\[/g).map(function(crumb) {
return crumb.replace(/\]$/, '').trim().replace(/^(["'])((?:(?!\1)[^\\]|\\.)*?)\1$/, (match, quote, str) => str.replace(/\\(\\)?/g, "$1"));
}).reduce(function(obj, prop) {
return obj ? obj[prop] : undefined;
}, obj);
}
// (IE9+) Two steps
var pathString = "[0]['property'].others[3].next['final']";
var obj = [{
property: {
others: [1, 2, 3, {
next: {
final: "SUCCESS"
}
}]
}
}];
// Turn string to path array
var pathArray = pathString
.replace(/\[["']?([\w]+)["']?\]/g,".$1")
.split(".")
.splice(1);
// Add object prototype method
Object.prototype.path = function (path) {
try {
return [this].concat(path).reduce(function (f, l) {
return f[l];
});
} catch (e) {
console.error(e);
}
};
// usage
console.log(obj.path(pathArray));
console.log(obj.path([0,"doesNotExist"]));

How to pass 'objectName.variableName' as a string, then operate on it as a variable? [duplicate]

I have a data structure like this :
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'
}
]
};
And I would like to access the data using these variable :
var part1name = "part1.name";
var part2quantity = "part2.qty";
var part3name1 = "part3[0].name";
part1name should be filled with someObject.part1.name 's value, which is "Part 1". Same thing with part2quantity which filled with 60.
Is there anyway to achieve this with either pure javascript or JQuery?
I just made this based on some similar code I already had, it appears to work:
Object.byString = function(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;
}
Usage::
Object.byString(someObj, 'part3[0].name');
See a working demo at http://jsfiddle.net/alnitak/hEsys/
EDIT some have noticed that this code will throw an error if passed a string where the left-most indexes don't correspond to a correctly nested entry within the object. This is a valid concern, but IMHO best addressed with a try / catch block when calling, rather than having this function silently return undefined for an invalid index.
This is now supported by lodash using _.get(obj, property). See https://lodash.com/docs#get
Example from the docs:
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// → 3
_.get(object, ['a', '0', 'b', 'c']);
// → 3
_.get(object, 'a.b.c', 'default');
// → 'default'
This is the solution I use:
function resolve(path, obj=self, separator='.') {
var properties = Array.isArray(path) ? path : path.split(separator)
return properties.reduce((prev, curr) => prev?.[curr], obj)
}
Example usage:
// accessing property path on global scope
resolve("document.body.style.width")
// or
resolve("style.width", document.body)
// accessing array indexes
// (someObject has been defined in the question)
resolve("part3.0.size", someObject) // returns '10'
// accessing non-existent properties
// returns undefined when intermediate properties are not defined:
resolve('properties.that.do.not.exist', {hello:'world'})
// accessing properties with unusual keys by changing the separator
var obj = { object: { 'a.property.name.with.periods': 42 } }
resolve('object->a.property.name.with.periods', obj, '->') // returns 42
// accessing properties with unusual keys by passing a property name array
resolve(['object', 'a.property.name.with.periods'], obj) // returns 42
Limitations:
Can't use brackets ([]) for array indices—though specifying array indices between the separator token (e.g., .) works fine as shown above.
ES6: Only one line in Vanila JS (it return null if don't find instead of giving error):
'path.string'.split('.').reduce((p,c)=>p&&p[c]||null, MyOBJ)
Or example:
'a.b.c'.split('.').reduce((p,c)=>p&&p[c]||null, {a:{b:{c:1}}})
With Optional chaining operator:
'a.b.c'.split('.').reduce((p,c)=>p?.[c], {a:{b:{c:1}}})
For a ready to use function that also recognizes false, 0 and negative number and accept default values as parameter:
const resolvePath = (object, path, defaultValue) => path
.split('.')
.reduce((o, p) => o ? o[p] : defaultValue, object)
Example to use:
resolvePath(window,'document.body') => <body>
resolvePath(window,'document.body.xyz') => undefined
resolvePath(window,'document.body.xyz', null) => null
resolvePath(window,'document.body.xyz', 1) => 1
Bonus:
To set a path (Requested by #rob-gordon) you can use:
const setPath = (object, path, value) => path
.split('.')
.reduce((o,p,i) => o[p] = path.split('.').length === ++i ? value : o[p] || {}, object)
Example:
let myVar = {}
setPath(myVar, 'a.b.c', 42) => 42
console.log(myVar) => {a: {b: {c: 42}}}
Access array with []:
const resolvePath = (object, path, defaultValue) => path
.split(/[\.\[\]\'\"]/)
.filter(p => p)
.reduce((o, p) => o ? o[p] : defaultValue, object)
Example:
const myVar = {a:{b:[{c:1}]}}
resolvePath(myVar,'a.b[0].c') => 1
resolvePath(myVar,'a["b"][\'0\'].c') => 1
You'd have to parse the string yourself:
function getProperty(obj, prop) {
var parts = prop.split('.');
if (Array.isArray(parts)) {
var last = parts.pop(),
l = parts.length,
i = 1,
current = parts[0];
while((obj = obj[current]) && i < l) {
current = parts[i];
i++;
}
if(obj) {
return obj[last];
}
} else {
throw 'parts is not valid array';
}
}
This required that you also define array indexes with dot notation:
var part3name1 = "part3.0.name";
It makes the parsing easier.
DEMO
Works for arrays / arrays inside the object also.
Defensive against invalid values.
/**
* Retrieve nested item from object/array
* #param {Object|Array} obj
* #param {String} path dot separated
* #param {*} def default value ( if result undefined )
* #returns {*}
*/
function path(obj, path, def){
var i, len;
for(i = 0,path = path.split('.'), len = path.length; i < len; i++){
if(!obj || typeof obj !== 'object') return def;
obj = obj[path[i]];
}
if(obj === undefined) return def;
return obj;
}
//////////////////////////
// TEST //
//////////////////////////
var arr = [true, {'sp ace': true}, true]
var obj = {
'sp ace': true,
arr: arr,
nested: {'dotted.str.ing': true},
arr3: arr
}
shouldThrow(`path(obj, "arr.0")`);
shouldBeDefined(`path(obj, "arr[0]")`);
shouldBeEqualToNumber(`path(obj, "arr.length")`, 3);
shouldBeTrue(`path(obj, "sp ace")`);
shouldBeEqualToString(`path(obj, "none.existed.prop", "fallback")`, "fallback");
shouldBeTrue(`path(obj, "nested['dotted.str.ing'])`);
<script src="https://cdn.rawgit.com/coderek/e7b30bac7634a50ad8fd/raw/174b6634c8f57aa8aac0716c5b7b2a7098e03584/js-test.js"></script>
This will probably never see the light of day... but here it is anyway.
Replace [] bracket syntax with .
Split on . character
Remove blank strings
Find the path (otherwise undefined)
(For finding a path to an object, use this pathTo solution.)
// "one liner" (ES6)
const deep_value = (obj, path) =>
path
.replace(/\[|\]\.?/g, '.')
.split('.')
.filter(s => s)
.reduce((acc, val) => acc && acc[val], obj);
// ... and that's 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'
}
// ...
],
'pa[rt3' : [
{
'name': 'Part 3A',
'size': '10',
'qty' : '20'
}
// ...
]
};
console.log(deep_value(someObject, "part1.name")); // Part 1
console.log(deep_value(someObject, "part2.qty")); // 60
console.log(deep_value(someObject, "part3[0].name")); // Part 3A
console.log(deep_value(someObject, "part3[0].....name")); // Part 3A - invalid blank paths removed
console.log(deep_value(someObject, "pa[rt3[0].name")); // undefined - name does not support square brackets
using eval:
var part1name = eval("someObject.part1.name");
wrap to return undefined on error
function path(obj, path) {
try {
return eval("obj." + path);
} catch(e) {
return undefined;
}
}
http://jsfiddle.net/shanimal/b3xTw/
Please use common sense and caution when wielding the power of eval. It's a bit like a light saber, if you turn it on there's a 90% chance you'll sever a limb. Its not for everybody.
You can manage to obtain the value of a deep object member with dot notation without any external JavaScript library with the following simple trick:
function objectGet(obj, path) { return new Function('_', 'return _.' + path)(obj); };
In your case to obtain value of part1.name from someObject just do:
objectGet(someObject, 'part1.name');
Here is a simple fiddle demo: https://jsfiddle.net/harishanchu/oq5esowf/
It's a one liner with lodash.
const deep = { l1: { l2: { l3: "Hello" } } };
const prop = "l1.l2.l3";
const val = _.reduce(prop.split('.'), function(result, value) { return result ? result[value] : undefined; }, deep);
// val === "Hello"
Or even better...
const val = _.get(deep, prop);
Or ES6 version w/ reduce...
const val = prop.split('.').reduce((r, val) => { return r ? r[val] : undefined; }, deep);
Plunkr
I think you are asking for this:
var part1name = someObject.part1.name;
var part2quantity = someObject.part2.qty;
var part3name1 = someObject.part3[0].name;
You could be asking for this:
var part1name = someObject["part1"]["name"];
var part2quantity = someObject["part2"]["qty"];
var part3name1 = someObject["part3"][0]["name"];
Both of which will work
Or maybe you are asking for this
var partName = "part1";
var nameStr = "name";
var part1name = someObject[partName][nameStr];
Finally you could be asking for this
var partName = "part1.name";
var partBits = partName.split(".");
var part1name = someObject[partBits[0]][partBits[1]];
Instead of trying to emulate JS syntax which you will have to spend a bunch of compute parsing, or just get wrong/forget things like a bunch of these answers (keys with .s in, anyone?), just use an array of keys.
var part1name = Object.get(someObject, ['part1', 'name']);
var part2quantity = Object.get(someObject, ['part2', 'qty']);
var part3name1 = Object.get(someObject, ['part3', 0, 'name']);
If you need to use a single string instead, simply JSONify it.
Another improvement in this method is that you can delete/set the root level object.
function resolve(obj, path) {
let root = obj = [obj];
path = [0, ...path];
while (path.length > 1)
obj = obj[path.shift()];
return [obj, path[0], root];
}
Object.get = (obj, path) => {
let [parent, key] = resolve(obj, path);
return parent[key];
};
Object.del = (obj, path) => {
let [parent, key, root] = resolve(obj, path);
delete parent[key];
return root[0];
};
Object.set = (obj, path, value) => {
let [parent, key, root] = resolve(obj, path);
parent[key] = value;
return root[0];
};
Demo of other features:
The bob = for .set(/.del( isn't necessary unless your path might be empty (manipulating the root object).
I prove that I don't clone the object by using steve to keep a reference to the original and checking bob == steve //true after that first .set(
Just in case, anyone's visiting this question in 2017 or later and looking for an easy-to-remember way, here's an elaborate blog post on Accessing Nested Objects in JavaScript without being bamboozled by
Cannot read property 'foo' of undefined error
Access Nested Objects Using Array Reduce
Let's take this example structure
const user = {
id: 101,
email: 'jack#dev.com',
personalInfo: {
name: 'Jack',
address: [{
line1: 'westwish st',
line2: 'washmasher',
city: 'wallas',
state: 'WX'
}]
}
}
To be able to access nested arrays, you can write your own array reduce util.
const getNestedObject = (nestedObj, pathArr) => {
return pathArr.reduce((obj, key) =>
(obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);
}
// pass in your object structure as array elements
const name = getNestedObject(user, ['personalInfo', 'name']);
// to access nested array, just pass in array index as an element the path array.
const city = getNestedObject(user, ['personalInfo', 'address', 0, 'city']);
// this will return the city from the first address item.
There is also an excellent type handling minimal library typy that does all this for you.
With typy, your code will look like this
const city = t(user, 'personalInfo.address[0].city').safeObject;
Disclaimer: I am the author of this package.
Here I offer more ways, which seem faster in many respects:
Option 1: Split string on . or [ or ] or ' or ", reverse it, skip empty items.
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var parts = path.split(/\[|\]|\.|'|"/g).reverse(), name; // (why reverse? because it's usually faster to pop off the end of an array)
while (parts.length) { name=parts.pop(); if (name) origin=origin[name]; }
return origin;
}
Option 2 (fastest of all, except eval): Low level character scan (no regex/split/etc, just a quick char scan).
Note: This one does not support quotes for indexes.
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var c = '', pc, i = 0, n = path.length, name = '';
if (n) while (i<=n) ((c = path[i++]) == '.' || c == '[' || c == ']' || c == void 0) ? (name?(origin = origin[name], name = ''):(pc=='.'||pc=='['||pc==']'&&c==']'?i=n+2:void 0),pc=c) : name += c;
if (i==n+2) throw "Invalid path: "+path;
return origin;
} // (around 1,000,000+/- ops/sec)
Option 3: (new: option 2 expanded to support quotes - a bit slower, but still fast)
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var c, pc, i = 0, n = path.length, name = '', q;
while (i<=n)
((c = path[i++]) == '.' || c == '[' || c == ']' || c == "'" || c == '"' || c == void 0) ? (c==q&&path[i]==']'?q='':q?name+=c:name?(origin?origin=origin[name]:i=n+2,name='') : (pc=='['&&(c=='"'||c=="'")?q=c:pc=='.'||pc=='['||pc==']'&&c==']'||pc=='"'||pc=="'"?i=n+2:void 0), pc=c) : name += c;
if (i==n+2 || name) throw "Invalid path: "+path;
return origin;
}
JSPerf: http://jsperf.com/ways-to-dereference-a-delimited-property-string/3
"eval(...)" is still king though (performance wise that is). If you have property paths directly under your control, there shouldn't be any issues with using 'eval' (especially if speed is desired). If pulling property paths "over the wire" (on the line!? lol :P), then yes, use something else to be safe. Only an idiot would say to never use "eval" at all, as there ARE good reasons when to use it. Also, "It is used in Doug Crockford's JSON parser." If the input is safe, then no problems at all. Use the right tool for the right job, that's it.
AngularJS
Speigg's approach is very neat and clean, though I found this reply while searching for the solution of accessing AngularJS $scope properties by string path and with a little modification it does the job:
$scope.resolve = function( path, obj ) {
return path.split('.').reduce( function( prev, curr ) {
return prev[curr];
}, obj || this );
}
Just place this function in your root controller and use it any child scope like this:
$scope.resolve( 'path.to.any.object.in.scope')
If you want a solution that can properly detect and report details of any issue with the path parsing, I wrote my own solution to this - library path-value.
const {resolveValue} = require('path-value');
resolveValue(someObject, 'part1.name'); //=> Part 1
resolveValue(someObject, 'part2.qty'); //=> 50
resolveValue(someObject, 'part3.0.name'); //=> Part 3A
Note that for indexes we use .0, and not [0], because parsing the latter adds a performance penalty, while .0 works directly in JavaScript, and is thus very fast.
However, full ES5 JavaScript syntax is also supported, it just needs to be tokenized first:
const {resolveValue, tokenizePath} = require('path-value');
const path = tokenizePath('part3[0].name'); //=> ['part3', '0', 'name']
resolveValue(someObject, path); //=> Part 3A
/**
* Access a deep value inside a object
* Works by passing a path like "foo.bar", also works with nested arrays like "foo[0][1].baz"
* #author Victor B. https://gist.github.com/victornpb/4c7882c1b9d36292308e
* Unit tests: http://jsfiddle.net/Victornpb/0u1qygrh/
*/
function getDeepVal(obj, path) {
if (typeof obj === "undefined" || obj === null) return;
path = path.split(/[\.\[\]\"\']{1,2}/);
for (var i = 0, l = path.length; i < l; i++) {
if (path[i] === "") continue;
obj = obj[path[i]];
if (typeof obj === "undefined" || obj === null) return;
}
return obj;
}
Works with
getDeepVal(obj,'foo.bar')
getDeepVal(obj,'foo.1.bar')
getDeepVal(obj,'foo[0].baz')
getDeepVal(obj,'foo[1][2]')
getDeepVal(obj,"foo['bar'].baz")
getDeepVal(obj,"foo['bar']['baz']")
getDeepVal(obj,"foo.bar.0.baz[1]['2']['w'].aaa[\"f\"].bb")
I haven't yet found a package to do all of the operations with a string path, so I ended up writing my own quick little package which supports insert(), get() (with default return), set() and remove() operations.
You can use dot notation, brackets, number indices, string number properties, and keys with non-word characters. Simple usage below:
> var jsocrud = require('jsocrud');
...
// Get (Read) ---
> var obj = {
> foo: [
> {
> 'key w/ non-word chars': 'bar'
> }
> ]
> };
undefined
> jsocrud.get(obj, '.foo[0]["key w/ non-word chars"]');
'bar'
https://www.npmjs.com/package/jsocrud
https://github.com/vertical-knowledge/jsocrud
Simple function, allowing for either a string or array path.
function get(obj, path) {
if(typeof path === 'string') path = path.split('.');
if(path.length === 0) return obj;
return get(obj[path[0]], path.slice(1));
}
const obj = {a: {b: {c: 'foo'}}};
console.log(get(obj, 'a.b.c')); //foo
OR
console.log(get(obj, ['a', 'b', 'c'])); //foo
There is an npm module now for doing this: https://github.com/erictrinh/safe-access
Example usage:
var access = require('safe-access');
access(very, 'nested.property.and.array[0]');
While reduce is good, I am surprised no one used forEach:
function valueForKeyPath(obj, path){
const keys = path.split('.');
keys.forEach((key)=> obj = obj[key]);
return obj;
};
Test
I'm developing online-shop with React. I tried to change values in copied state object to update original state with it on submit.
Examples above haven't worked for me, because most of them mutate structure of copied object. I found working example of the function for accessing and changing values of the deep nested object properties: https://lowrey.me/create-an-object-by-path-in-javascript-2/ Here it is:
const createPath = (obj, path, value = null) => {
path = typeof path === 'string' ? path.split('.') : path;
let current = obj;
while (path.length > 1) {
const [head, ...tail] = path;
path = tail;
if (current[head] === undefined) {
current[head] = {};
}
current = current[head];
}
current[path[0]] = value;
return obj;
};
You can use ramda library.
Learning ramda also helps you to work with immutable objects easily.
var obj = {
a:{
b: {
c:[100,101,{
d: 1000
}]
}
}
};
var lens = R.lensPath('a.b.c.2.d'.split('.'));
var result = R.view(lens, obj);
https://codepen.io/ghominejad/pen/BayJZOQ
Based on Alnitak's answer.
I wrapped the polyfill in a check, and reduced the function to a single chained reduction.
if (Object.byPath === undefined) {
Object.byPath = (obj, path) => path
.replace(/\[(\w+)\]/g, '.$1')
.replace(/^\./, '')
.split(/\./g)
.reduce((ref, key) => key in ref ? ref[key] : ref, obj)
}
const data = {
foo: {
bar: [{
baz: 1
}]
}
}
console.log(Object.byPath(data, 'foo.bar[0].baz'))
This can be simplified by splitting the logic into three separate functions:
const isVal = a => a != null; // everything except undefined + null
const prop = prop => obj => {
if (isVal(obj)) {
const value = obj[prop];
if (isVal(value)) return value;
else return undefined;
} else return undefined;
};
const path = paths => obj => {
const pathList = typeof paths === 'string' ? paths.split('.') : paths;
return pathList.reduce((value, key) => prop(key)(value), obj);
};
//usage:
const myObject = { foo: { bar: { baz: 'taco' } } };
const result = path('foo.bar')(myObject);
//results => { baz: 'taco' }
This variation supports:
passing an array or string argument
dealing with undefined values during invocation and execution
testing each function independently
using each function independently
DotObject = obj => new Proxy(obj, {
get: function(o,k) {
const m = k.match(/(.+?)\.(.+)/)
return m ? this.get(o[m[1]], m[2]) : o[k]
}
})
const test = DotObject({a: {b: {c: 'wow'}}})
console.log(test['a.b.c'])
If you need to access different nested key without knowing it at coding time (it will be trivial to address them) you can use the array notation accessor:
var part1name = someObject['part1']['name'];
var part2quantity = someObject['part2']['qty'];
var part3name1 = someObject['part3'][0]['name'];
They are equivalent to the dot notation accessor and may vary at runtime, for example:
var part = 'part1';
var property = 'name';
var part1name = someObject[part][property];
is equivalent to
var part1name = someObject['part1']['name'];
or
var part1name = someObject.part1.name;
I hope this address your question...
EDIT
I won't use a string to mantain a sort of xpath query to access an object value.
As you have to call a function to parse the query and retrieve the value I would follow another path (not :
var part1name = function(){ return this.part1.name; }
var part2quantity = function() { return this['part2']['qty']; }
var part3name1 = function() { return this.part3[0]['name'];}
// usage: part1name.apply(someObject);
or, if you are uneasy with the apply method
var part1name = function(obj){ return obj.part1.name; }
var part2quantity = function(obj) { return obj['part2']['qty']; }
var part3name1 = function(obj) { return obj.part3[0]['name'];}
// usage: part1name(someObject);
The functions are shorter, clearer, the interpreter check them for you for syntax errors and so on.
By the way, I feel that a simple assignment made at right time will be sufficent...
Just had the same question recently and successfully used https://npmjs.org/package/tea-properties which also set nested object/arrays :
get:
var o = {
prop: {
arr: [
{foo: 'bar'}
]
}
};
var properties = require('tea-properties');
var value = properties.get(o, 'prop.arr[0].foo');
assert(value, 'bar'); // true
set:
var o = {};
var properties = require('tea-properties');
properties.set(o, 'prop.arr[0].foo', 'bar');
assert(o.prop.arr[0].foo, 'bar'); // true
Based on a previous answer, I have created a function that can also handle brackets. But no dots inside them due to the split.
function get(obj, str) {
return str.split(/\.|\[/g).map(function(crumb) {
return crumb.replace(/\]$/, '').trim().replace(/^(["'])((?:(?!\1)[^\\]|\\.)*?)\1$/, (match, quote, str) => str.replace(/\\(\\)?/g, "$1"));
}).reduce(function(obj, prop) {
return obj ? obj[prop] : undefined;
}, obj);
}
// (IE9+) Two steps
var pathString = "[0]['property'].others[3].next['final']";
var obj = [{
property: {
others: [1, 2, 3, {
next: {
final: "SUCCESS"
}
}]
}
}];
// Turn string to path array
var pathArray = pathString
.replace(/\[["']?([\w]+)["']?\]/g,".$1")
.split(".")
.splice(1);
// Add object prototype method
Object.prototype.path = function (path) {
try {
return [this].concat(path).reduce(function (f, l) {
return f[l];
});
} catch (e) {
console.error(e);
}
};
// usage
console.log(obj.path(pathArray));
console.log(obj.path([0,"doesNotExist"]));

Delete nested property from javascript object by path [duplicate]

I have a data structure like this :
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'
}
]
};
And I would like to access the data using these variable :
var part1name = "part1.name";
var part2quantity = "part2.qty";
var part3name1 = "part3[0].name";
part1name should be filled with someObject.part1.name 's value, which is "Part 1". Same thing with part2quantity which filled with 60.
Is there anyway to achieve this with either pure javascript or JQuery?
I just made this based on some similar code I already had, it appears to work:
Object.byString = function(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;
}
Usage::
Object.byString(someObj, 'part3[0].name');
See a working demo at http://jsfiddle.net/alnitak/hEsys/
EDIT some have noticed that this code will throw an error if passed a string where the left-most indexes don't correspond to a correctly nested entry within the object. This is a valid concern, but IMHO best addressed with a try / catch block when calling, rather than having this function silently return undefined for an invalid index.
This is now supported by lodash using _.get(obj, property). See https://lodash.com/docs#get
Example from the docs:
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// → 3
_.get(object, ['a', '0', 'b', 'c']);
// → 3
_.get(object, 'a.b.c', 'default');
// → 'default'
This is the solution I use:
function resolve(path, obj=self, separator='.') {
var properties = Array.isArray(path) ? path : path.split(separator)
return properties.reduce((prev, curr) => prev?.[curr], obj)
}
Example usage:
// accessing property path on global scope
resolve("document.body.style.width")
// or
resolve("style.width", document.body)
// accessing array indexes
// (someObject has been defined in the question)
resolve("part3.0.size", someObject) // returns '10'
// accessing non-existent properties
// returns undefined when intermediate properties are not defined:
resolve('properties.that.do.not.exist', {hello:'world'})
// accessing properties with unusual keys by changing the separator
var obj = { object: { 'a.property.name.with.periods': 42 } }
resolve('object->a.property.name.with.periods', obj, '->') // returns 42
// accessing properties with unusual keys by passing a property name array
resolve(['object', 'a.property.name.with.periods'], obj) // returns 42
Limitations:
Can't use brackets ([]) for array indices—though specifying array indices between the separator token (e.g., .) works fine as shown above.
ES6: Only one line in Vanila JS (it return null if don't find instead of giving error):
'path.string'.split('.').reduce((p,c)=>p&&p[c]||null, MyOBJ)
Or example:
'a.b.c'.split('.').reduce((p,c)=>p&&p[c]||null, {a:{b:{c:1}}})
With Optional chaining operator:
'a.b.c'.split('.').reduce((p,c)=>p?.[c], {a:{b:{c:1}}})
For a ready to use function that also recognizes false, 0 and negative number and accept default values as parameter:
const resolvePath = (object, path, defaultValue) => path
.split('.')
.reduce((o, p) => o ? o[p] : defaultValue, object)
Example to use:
resolvePath(window,'document.body') => <body>
resolvePath(window,'document.body.xyz') => undefined
resolvePath(window,'document.body.xyz', null) => null
resolvePath(window,'document.body.xyz', 1) => 1
Bonus:
To set a path (Requested by #rob-gordon) you can use:
const setPath = (object, path, value) => path
.split('.')
.reduce((o,p,i) => o[p] = path.split('.').length === ++i ? value : o[p] || {}, object)
Example:
let myVar = {}
setPath(myVar, 'a.b.c', 42) => 42
console.log(myVar) => {a: {b: {c: 42}}}
Access array with []:
const resolvePath = (object, path, defaultValue) => path
.split(/[\.\[\]\'\"]/)
.filter(p => p)
.reduce((o, p) => o ? o[p] : defaultValue, object)
Example:
const myVar = {a:{b:[{c:1}]}}
resolvePath(myVar,'a.b[0].c') => 1
resolvePath(myVar,'a["b"][\'0\'].c') => 1
You'd have to parse the string yourself:
function getProperty(obj, prop) {
var parts = prop.split('.');
if (Array.isArray(parts)) {
var last = parts.pop(),
l = parts.length,
i = 1,
current = parts[0];
while((obj = obj[current]) && i < l) {
current = parts[i];
i++;
}
if(obj) {
return obj[last];
}
} else {
throw 'parts is not valid array';
}
}
This required that you also define array indexes with dot notation:
var part3name1 = "part3.0.name";
It makes the parsing easier.
DEMO
Works for arrays / arrays inside the object also.
Defensive against invalid values.
/**
* Retrieve nested item from object/array
* #param {Object|Array} obj
* #param {String} path dot separated
* #param {*} def default value ( if result undefined )
* #returns {*}
*/
function path(obj, path, def){
var i, len;
for(i = 0,path = path.split('.'), len = path.length; i < len; i++){
if(!obj || typeof obj !== 'object') return def;
obj = obj[path[i]];
}
if(obj === undefined) return def;
return obj;
}
//////////////////////////
// TEST //
//////////////////////////
var arr = [true, {'sp ace': true}, true]
var obj = {
'sp ace': true,
arr: arr,
nested: {'dotted.str.ing': true},
arr3: arr
}
shouldThrow(`path(obj, "arr.0")`);
shouldBeDefined(`path(obj, "arr[0]")`);
shouldBeEqualToNumber(`path(obj, "arr.length")`, 3);
shouldBeTrue(`path(obj, "sp ace")`);
shouldBeEqualToString(`path(obj, "none.existed.prop", "fallback")`, "fallback");
shouldBeTrue(`path(obj, "nested['dotted.str.ing'])`);
<script src="https://cdn.rawgit.com/coderek/e7b30bac7634a50ad8fd/raw/174b6634c8f57aa8aac0716c5b7b2a7098e03584/js-test.js"></script>
This will probably never see the light of day... but here it is anyway.
Replace [] bracket syntax with .
Split on . character
Remove blank strings
Find the path (otherwise undefined)
(For finding a path to an object, use this pathTo solution.)
// "one liner" (ES6)
const deep_value = (obj, path) =>
path
.replace(/\[|\]\.?/g, '.')
.split('.')
.filter(s => s)
.reduce((acc, val) => acc && acc[val], obj);
// ... and that's 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'
}
// ...
],
'pa[rt3' : [
{
'name': 'Part 3A',
'size': '10',
'qty' : '20'
}
// ...
]
};
console.log(deep_value(someObject, "part1.name")); // Part 1
console.log(deep_value(someObject, "part2.qty")); // 60
console.log(deep_value(someObject, "part3[0].name")); // Part 3A
console.log(deep_value(someObject, "part3[0].....name")); // Part 3A - invalid blank paths removed
console.log(deep_value(someObject, "pa[rt3[0].name")); // undefined - name does not support square brackets
using eval:
var part1name = eval("someObject.part1.name");
wrap to return undefined on error
function path(obj, path) {
try {
return eval("obj." + path);
} catch(e) {
return undefined;
}
}
http://jsfiddle.net/shanimal/b3xTw/
Please use common sense and caution when wielding the power of eval. It's a bit like a light saber, if you turn it on there's a 90% chance you'll sever a limb. Its not for everybody.
You can manage to obtain the value of a deep object member with dot notation without any external JavaScript library with the following simple trick:
function objectGet(obj, path) { return new Function('_', 'return _.' + path)(obj); };
In your case to obtain value of part1.name from someObject just do:
objectGet(someObject, 'part1.name');
Here is a simple fiddle demo: https://jsfiddle.net/harishanchu/oq5esowf/
It's a one liner with lodash.
const deep = { l1: { l2: { l3: "Hello" } } };
const prop = "l1.l2.l3";
const val = _.reduce(prop.split('.'), function(result, value) { return result ? result[value] : undefined; }, deep);
// val === "Hello"
Or even better...
const val = _.get(deep, prop);
Or ES6 version w/ reduce...
const val = prop.split('.').reduce((r, val) => { return r ? r[val] : undefined; }, deep);
Plunkr
I think you are asking for this:
var part1name = someObject.part1.name;
var part2quantity = someObject.part2.qty;
var part3name1 = someObject.part3[0].name;
You could be asking for this:
var part1name = someObject["part1"]["name"];
var part2quantity = someObject["part2"]["qty"];
var part3name1 = someObject["part3"][0]["name"];
Both of which will work
Or maybe you are asking for this
var partName = "part1";
var nameStr = "name";
var part1name = someObject[partName][nameStr];
Finally you could be asking for this
var partName = "part1.name";
var partBits = partName.split(".");
var part1name = someObject[partBits[0]][partBits[1]];
Instead of trying to emulate JS syntax which you will have to spend a bunch of compute parsing, or just get wrong/forget things like a bunch of these answers (keys with .s in, anyone?), just use an array of keys.
var part1name = Object.get(someObject, ['part1', 'name']);
var part2quantity = Object.get(someObject, ['part2', 'qty']);
var part3name1 = Object.get(someObject, ['part3', 0, 'name']);
If you need to use a single string instead, simply JSONify it.
Another improvement in this method is that you can delete/set the root level object.
function resolve(obj, path) {
let root = obj = [obj];
path = [0, ...path];
while (path.length > 1)
obj = obj[path.shift()];
return [obj, path[0], root];
}
Object.get = (obj, path) => {
let [parent, key] = resolve(obj, path);
return parent[key];
};
Object.del = (obj, path) => {
let [parent, key, root] = resolve(obj, path);
delete parent[key];
return root[0];
};
Object.set = (obj, path, value) => {
let [parent, key, root] = resolve(obj, path);
parent[key] = value;
return root[0];
};
Demo of other features:
The bob = for .set(/.del( isn't necessary unless your path might be empty (manipulating the root object).
I prove that I don't clone the object by using steve to keep a reference to the original and checking bob == steve //true after that first .set(
Just in case, anyone's visiting this question in 2017 or later and looking for an easy-to-remember way, here's an elaborate blog post on Accessing Nested Objects in JavaScript without being bamboozled by
Cannot read property 'foo' of undefined error
Access Nested Objects Using Array Reduce
Let's take this example structure
const user = {
id: 101,
email: 'jack#dev.com',
personalInfo: {
name: 'Jack',
address: [{
line1: 'westwish st',
line2: 'washmasher',
city: 'wallas',
state: 'WX'
}]
}
}
To be able to access nested arrays, you can write your own array reduce util.
const getNestedObject = (nestedObj, pathArr) => {
return pathArr.reduce((obj, key) =>
(obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);
}
// pass in your object structure as array elements
const name = getNestedObject(user, ['personalInfo', 'name']);
// to access nested array, just pass in array index as an element the path array.
const city = getNestedObject(user, ['personalInfo', 'address', 0, 'city']);
// this will return the city from the first address item.
There is also an excellent type handling minimal library typy that does all this for you.
With typy, your code will look like this
const city = t(user, 'personalInfo.address[0].city').safeObject;
Disclaimer: I am the author of this package.
Here I offer more ways, which seem faster in many respects:
Option 1: Split string on . or [ or ] or ' or ", reverse it, skip empty items.
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var parts = path.split(/\[|\]|\.|'|"/g).reverse(), name; // (why reverse? because it's usually faster to pop off the end of an array)
while (parts.length) { name=parts.pop(); if (name) origin=origin[name]; }
return origin;
}
Option 2 (fastest of all, except eval): Low level character scan (no regex/split/etc, just a quick char scan).
Note: This one does not support quotes for indexes.
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var c = '', pc, i = 0, n = path.length, name = '';
if (n) while (i<=n) ((c = path[i++]) == '.' || c == '[' || c == ']' || c == void 0) ? (name?(origin = origin[name], name = ''):(pc=='.'||pc=='['||pc==']'&&c==']'?i=n+2:void 0),pc=c) : name += c;
if (i==n+2) throw "Invalid path: "+path;
return origin;
} // (around 1,000,000+/- ops/sec)
Option 3: (new: option 2 expanded to support quotes - a bit slower, but still fast)
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var c, pc, i = 0, n = path.length, name = '', q;
while (i<=n)
((c = path[i++]) == '.' || c == '[' || c == ']' || c == "'" || c == '"' || c == void 0) ? (c==q&&path[i]==']'?q='':q?name+=c:name?(origin?origin=origin[name]:i=n+2,name='') : (pc=='['&&(c=='"'||c=="'")?q=c:pc=='.'||pc=='['||pc==']'&&c==']'||pc=='"'||pc=="'"?i=n+2:void 0), pc=c) : name += c;
if (i==n+2 || name) throw "Invalid path: "+path;
return origin;
}
JSPerf: http://jsperf.com/ways-to-dereference-a-delimited-property-string/3
"eval(...)" is still king though (performance wise that is). If you have property paths directly under your control, there shouldn't be any issues with using 'eval' (especially if speed is desired). If pulling property paths "over the wire" (on the line!? lol :P), then yes, use something else to be safe. Only an idiot would say to never use "eval" at all, as there ARE good reasons when to use it. Also, "It is used in Doug Crockford's JSON parser." If the input is safe, then no problems at all. Use the right tool for the right job, that's it.
AngularJS
Speigg's approach is very neat and clean, though I found this reply while searching for the solution of accessing AngularJS $scope properties by string path and with a little modification it does the job:
$scope.resolve = function( path, obj ) {
return path.split('.').reduce( function( prev, curr ) {
return prev[curr];
}, obj || this );
}
Just place this function in your root controller and use it any child scope like this:
$scope.resolve( 'path.to.any.object.in.scope')
If you want a solution that can properly detect and report details of any issue with the path parsing, I wrote my own solution to this - library path-value.
const {resolveValue} = require('path-value');
resolveValue(someObject, 'part1.name'); //=> Part 1
resolveValue(someObject, 'part2.qty'); //=> 50
resolveValue(someObject, 'part3.0.name'); //=> Part 3A
Note that for indexes we use .0, and not [0], because parsing the latter adds a performance penalty, while .0 works directly in JavaScript, and is thus very fast.
However, full ES5 JavaScript syntax is also supported, it just needs to be tokenized first:
const {resolveValue, tokenizePath} = require('path-value');
const path = tokenizePath('part3[0].name'); //=> ['part3', '0', 'name']
resolveValue(someObject, path); //=> Part 3A
/**
* Access a deep value inside a object
* Works by passing a path like "foo.bar", also works with nested arrays like "foo[0][1].baz"
* #author Victor B. https://gist.github.com/victornpb/4c7882c1b9d36292308e
* Unit tests: http://jsfiddle.net/Victornpb/0u1qygrh/
*/
function getDeepVal(obj, path) {
if (typeof obj === "undefined" || obj === null) return;
path = path.split(/[\.\[\]\"\']{1,2}/);
for (var i = 0, l = path.length; i < l; i++) {
if (path[i] === "") continue;
obj = obj[path[i]];
if (typeof obj === "undefined" || obj === null) return;
}
return obj;
}
Works with
getDeepVal(obj,'foo.bar')
getDeepVal(obj,'foo.1.bar')
getDeepVal(obj,'foo[0].baz')
getDeepVal(obj,'foo[1][2]')
getDeepVal(obj,"foo['bar'].baz")
getDeepVal(obj,"foo['bar']['baz']")
getDeepVal(obj,"foo.bar.0.baz[1]['2']['w'].aaa[\"f\"].bb")
I haven't yet found a package to do all of the operations with a string path, so I ended up writing my own quick little package which supports insert(), get() (with default return), set() and remove() operations.
You can use dot notation, brackets, number indices, string number properties, and keys with non-word characters. Simple usage below:
> var jsocrud = require('jsocrud');
...
// Get (Read) ---
> var obj = {
> foo: [
> {
> 'key w/ non-word chars': 'bar'
> }
> ]
> };
undefined
> jsocrud.get(obj, '.foo[0]["key w/ non-word chars"]');
'bar'
https://www.npmjs.com/package/jsocrud
https://github.com/vertical-knowledge/jsocrud
Simple function, allowing for either a string or array path.
function get(obj, path) {
if(typeof path === 'string') path = path.split('.');
if(path.length === 0) return obj;
return get(obj[path[0]], path.slice(1));
}
const obj = {a: {b: {c: 'foo'}}};
console.log(get(obj, 'a.b.c')); //foo
OR
console.log(get(obj, ['a', 'b', 'c'])); //foo
There is an npm module now for doing this: https://github.com/erictrinh/safe-access
Example usage:
var access = require('safe-access');
access(very, 'nested.property.and.array[0]');
While reduce is good, I am surprised no one used forEach:
function valueForKeyPath(obj, path){
const keys = path.split('.');
keys.forEach((key)=> obj = obj[key]);
return obj;
};
Test
I'm developing online-shop with React. I tried to change values in copied state object to update original state with it on submit.
Examples above haven't worked for me, because most of them mutate structure of copied object. I found working example of the function for accessing and changing values of the deep nested object properties: https://lowrey.me/create-an-object-by-path-in-javascript-2/ Here it is:
const createPath = (obj, path, value = null) => {
path = typeof path === 'string' ? path.split('.') : path;
let current = obj;
while (path.length > 1) {
const [head, ...tail] = path;
path = tail;
if (current[head] === undefined) {
current[head] = {};
}
current = current[head];
}
current[path[0]] = value;
return obj;
};
You can use ramda library.
Learning ramda also helps you to work with immutable objects easily.
var obj = {
a:{
b: {
c:[100,101,{
d: 1000
}]
}
}
};
var lens = R.lensPath('a.b.c.2.d'.split('.'));
var result = R.view(lens, obj);
https://codepen.io/ghominejad/pen/BayJZOQ
Based on Alnitak's answer.
I wrapped the polyfill in a check, and reduced the function to a single chained reduction.
if (Object.byPath === undefined) {
Object.byPath = (obj, path) => path
.replace(/\[(\w+)\]/g, '.$1')
.replace(/^\./, '')
.split(/\./g)
.reduce((ref, key) => key in ref ? ref[key] : ref, obj)
}
const data = {
foo: {
bar: [{
baz: 1
}]
}
}
console.log(Object.byPath(data, 'foo.bar[0].baz'))
This can be simplified by splitting the logic into three separate functions:
const isVal = a => a != null; // everything except undefined + null
const prop = prop => obj => {
if (isVal(obj)) {
const value = obj[prop];
if (isVal(value)) return value;
else return undefined;
} else return undefined;
};
const path = paths => obj => {
const pathList = typeof paths === 'string' ? paths.split('.') : paths;
return pathList.reduce((value, key) => prop(key)(value), obj);
};
//usage:
const myObject = { foo: { bar: { baz: 'taco' } } };
const result = path('foo.bar')(myObject);
//results => { baz: 'taco' }
This variation supports:
passing an array or string argument
dealing with undefined values during invocation and execution
testing each function independently
using each function independently
DotObject = obj => new Proxy(obj, {
get: function(o,k) {
const m = k.match(/(.+?)\.(.+)/)
return m ? this.get(o[m[1]], m[2]) : o[k]
}
})
const test = DotObject({a: {b: {c: 'wow'}}})
console.log(test['a.b.c'])
If you need to access different nested key without knowing it at coding time (it will be trivial to address them) you can use the array notation accessor:
var part1name = someObject['part1']['name'];
var part2quantity = someObject['part2']['qty'];
var part3name1 = someObject['part3'][0]['name'];
They are equivalent to the dot notation accessor and may vary at runtime, for example:
var part = 'part1';
var property = 'name';
var part1name = someObject[part][property];
is equivalent to
var part1name = someObject['part1']['name'];
or
var part1name = someObject.part1.name;
I hope this address your question...
EDIT
I won't use a string to mantain a sort of xpath query to access an object value.
As you have to call a function to parse the query and retrieve the value I would follow another path (not :
var part1name = function(){ return this.part1.name; }
var part2quantity = function() { return this['part2']['qty']; }
var part3name1 = function() { return this.part3[0]['name'];}
// usage: part1name.apply(someObject);
or, if you are uneasy with the apply method
var part1name = function(obj){ return obj.part1.name; }
var part2quantity = function(obj) { return obj['part2']['qty']; }
var part3name1 = function(obj) { return obj.part3[0]['name'];}
// usage: part1name(someObject);
The functions are shorter, clearer, the interpreter check them for you for syntax errors and so on.
By the way, I feel that a simple assignment made at right time will be sufficent...
Just had the same question recently and successfully used https://npmjs.org/package/tea-properties which also set nested object/arrays :
get:
var o = {
prop: {
arr: [
{foo: 'bar'}
]
}
};
var properties = require('tea-properties');
var value = properties.get(o, 'prop.arr[0].foo');
assert(value, 'bar'); // true
set:
var o = {};
var properties = require('tea-properties');
properties.set(o, 'prop.arr[0].foo', 'bar');
assert(o.prop.arr[0].foo, 'bar'); // true
Based on a previous answer, I have created a function that can also handle brackets. But no dots inside them due to the split.
function get(obj, str) {
return str.split(/\.|\[/g).map(function(crumb) {
return crumb.replace(/\]$/, '').trim().replace(/^(["'])((?:(?!\1)[^\\]|\\.)*?)\1$/, (match, quote, str) => str.replace(/\\(\\)?/g, "$1"));
}).reduce(function(obj, prop) {
return obj ? obj[prop] : undefined;
}, obj);
}
// (IE9+) Two steps
var pathString = "[0]['property'].others[3].next['final']";
var obj = [{
property: {
others: [1, 2, 3, {
next: {
final: "SUCCESS"
}
}]
}
}];
// Turn string to path array
var pathArray = pathString
.replace(/\[["']?([\w]+)["']?\]/g,".$1")
.split(".")
.splice(1);
// Add object prototype method
Object.prototype.path = function (path) {
try {
return [this].concat(path).reduce(function (f, l) {
return f[l];
});
} catch (e) {
console.error(e);
}
};
// usage
console.log(obj.path(pathArray));
console.log(obj.path([0,"doesNotExist"]));

Javascript Make object with variable path [duplicate]

I have a data structure like this :
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'
}
]
};
And I would like to access the data using these variable :
var part1name = "part1.name";
var part2quantity = "part2.qty";
var part3name1 = "part3[0].name";
part1name should be filled with someObject.part1.name 's value, which is "Part 1". Same thing with part2quantity which filled with 60.
Is there anyway to achieve this with either pure javascript or JQuery?
I just made this based on some similar code I already had, it appears to work:
Object.byString = function(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;
}
Usage::
Object.byString(someObj, 'part3[0].name');
See a working demo at http://jsfiddle.net/alnitak/hEsys/
EDIT some have noticed that this code will throw an error if passed a string where the left-most indexes don't correspond to a correctly nested entry within the object. This is a valid concern, but IMHO best addressed with a try / catch block when calling, rather than having this function silently return undefined for an invalid index.
This is now supported by lodash using _.get(obj, property). See https://lodash.com/docs#get
Example from the docs:
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// → 3
_.get(object, ['a', '0', 'b', 'c']);
// → 3
_.get(object, 'a.b.c', 'default');
// → 'default'
This is the solution I use:
function resolve(path, obj=self, separator='.') {
var properties = Array.isArray(path) ? path : path.split(separator)
return properties.reduce((prev, curr) => prev?.[curr], obj)
}
Example usage:
// accessing property path on global scope
resolve("document.body.style.width")
// or
resolve("style.width", document.body)
// accessing array indexes
// (someObject has been defined in the question)
resolve("part3.0.size", someObject) // returns '10'
// accessing non-existent properties
// returns undefined when intermediate properties are not defined:
resolve('properties.that.do.not.exist', {hello:'world'})
// accessing properties with unusual keys by changing the separator
var obj = { object: { 'a.property.name.with.periods': 42 } }
resolve('object->a.property.name.with.periods', obj, '->') // returns 42
// accessing properties with unusual keys by passing a property name array
resolve(['object', 'a.property.name.with.periods'], obj) // returns 42
Limitations:
Can't use brackets ([]) for array indices—though specifying array indices between the separator token (e.g., .) works fine as shown above.
ES6: Only one line in Vanila JS (it return null if don't find instead of giving error):
'path.string'.split('.').reduce((p,c)=>p&&p[c]||null, MyOBJ)
Or example:
'a.b.c'.split('.').reduce((p,c)=>p&&p[c]||null, {a:{b:{c:1}}})
With Optional chaining operator:
'a.b.c'.split('.').reduce((p,c)=>p?.[c], {a:{b:{c:1}}})
For a ready to use function that also recognizes false, 0 and negative number and accept default values as parameter:
const resolvePath = (object, path, defaultValue) => path
.split('.')
.reduce((o, p) => o ? o[p] : defaultValue, object)
Example to use:
resolvePath(window,'document.body') => <body>
resolvePath(window,'document.body.xyz') => undefined
resolvePath(window,'document.body.xyz', null) => null
resolvePath(window,'document.body.xyz', 1) => 1
Bonus:
To set a path (Requested by #rob-gordon) you can use:
const setPath = (object, path, value) => path
.split('.')
.reduce((o,p,i) => o[p] = path.split('.').length === ++i ? value : o[p] || {}, object)
Example:
let myVar = {}
setPath(myVar, 'a.b.c', 42) => 42
console.log(myVar) => {a: {b: {c: 42}}}
Access array with []:
const resolvePath = (object, path, defaultValue) => path
.split(/[\.\[\]\'\"]/)
.filter(p => p)
.reduce((o, p) => o ? o[p] : defaultValue, object)
Example:
const myVar = {a:{b:[{c:1}]}}
resolvePath(myVar,'a.b[0].c') => 1
resolvePath(myVar,'a["b"][\'0\'].c') => 1
You'd have to parse the string yourself:
function getProperty(obj, prop) {
var parts = prop.split('.');
if (Array.isArray(parts)) {
var last = parts.pop(),
l = parts.length,
i = 1,
current = parts[0];
while((obj = obj[current]) && i < l) {
current = parts[i];
i++;
}
if(obj) {
return obj[last];
}
} else {
throw 'parts is not valid array';
}
}
This required that you also define array indexes with dot notation:
var part3name1 = "part3.0.name";
It makes the parsing easier.
DEMO
Works for arrays / arrays inside the object also.
Defensive against invalid values.
/**
* Retrieve nested item from object/array
* #param {Object|Array} obj
* #param {String} path dot separated
* #param {*} def default value ( if result undefined )
* #returns {*}
*/
function path(obj, path, def){
var i, len;
for(i = 0,path = path.split('.'), len = path.length; i < len; i++){
if(!obj || typeof obj !== 'object') return def;
obj = obj[path[i]];
}
if(obj === undefined) return def;
return obj;
}
//////////////////////////
// TEST //
//////////////////////////
var arr = [true, {'sp ace': true}, true]
var obj = {
'sp ace': true,
arr: arr,
nested: {'dotted.str.ing': true},
arr3: arr
}
shouldThrow(`path(obj, "arr.0")`);
shouldBeDefined(`path(obj, "arr[0]")`);
shouldBeEqualToNumber(`path(obj, "arr.length")`, 3);
shouldBeTrue(`path(obj, "sp ace")`);
shouldBeEqualToString(`path(obj, "none.existed.prop", "fallback")`, "fallback");
shouldBeTrue(`path(obj, "nested['dotted.str.ing'])`);
<script src="https://cdn.rawgit.com/coderek/e7b30bac7634a50ad8fd/raw/174b6634c8f57aa8aac0716c5b7b2a7098e03584/js-test.js"></script>
This will probably never see the light of day... but here it is anyway.
Replace [] bracket syntax with .
Split on . character
Remove blank strings
Find the path (otherwise undefined)
(For finding a path to an object, use this pathTo solution.)
// "one liner" (ES6)
const deep_value = (obj, path) =>
path
.replace(/\[|\]\.?/g, '.')
.split('.')
.filter(s => s)
.reduce((acc, val) => acc && acc[val], obj);
// ... and that's 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'
}
// ...
],
'pa[rt3' : [
{
'name': 'Part 3A',
'size': '10',
'qty' : '20'
}
// ...
]
};
console.log(deep_value(someObject, "part1.name")); // Part 1
console.log(deep_value(someObject, "part2.qty")); // 60
console.log(deep_value(someObject, "part3[0].name")); // Part 3A
console.log(deep_value(someObject, "part3[0].....name")); // Part 3A - invalid blank paths removed
console.log(deep_value(someObject, "pa[rt3[0].name")); // undefined - name does not support square brackets
using eval:
var part1name = eval("someObject.part1.name");
wrap to return undefined on error
function path(obj, path) {
try {
return eval("obj." + path);
} catch(e) {
return undefined;
}
}
http://jsfiddle.net/shanimal/b3xTw/
Please use common sense and caution when wielding the power of eval. It's a bit like a light saber, if you turn it on there's a 90% chance you'll sever a limb. Its not for everybody.
You can manage to obtain the value of a deep object member with dot notation without any external JavaScript library with the following simple trick:
function objectGet(obj, path) { return new Function('_', 'return _.' + path)(obj); };
In your case to obtain value of part1.name from someObject just do:
objectGet(someObject, 'part1.name');
Here is a simple fiddle demo: https://jsfiddle.net/harishanchu/oq5esowf/
It's a one liner with lodash.
const deep = { l1: { l2: { l3: "Hello" } } };
const prop = "l1.l2.l3";
const val = _.reduce(prop.split('.'), function(result, value) { return result ? result[value] : undefined; }, deep);
// val === "Hello"
Or even better...
const val = _.get(deep, prop);
Or ES6 version w/ reduce...
const val = prop.split('.').reduce((r, val) => { return r ? r[val] : undefined; }, deep);
Plunkr
I think you are asking for this:
var part1name = someObject.part1.name;
var part2quantity = someObject.part2.qty;
var part3name1 = someObject.part3[0].name;
You could be asking for this:
var part1name = someObject["part1"]["name"];
var part2quantity = someObject["part2"]["qty"];
var part3name1 = someObject["part3"][0]["name"];
Both of which will work
Or maybe you are asking for this
var partName = "part1";
var nameStr = "name";
var part1name = someObject[partName][nameStr];
Finally you could be asking for this
var partName = "part1.name";
var partBits = partName.split(".");
var part1name = someObject[partBits[0]][partBits[1]];
Instead of trying to emulate JS syntax which you will have to spend a bunch of compute parsing, or just get wrong/forget things like a bunch of these answers (keys with .s in, anyone?), just use an array of keys.
var part1name = Object.get(someObject, ['part1', 'name']);
var part2quantity = Object.get(someObject, ['part2', 'qty']);
var part3name1 = Object.get(someObject, ['part3', 0, 'name']);
If you need to use a single string instead, simply JSONify it.
Another improvement in this method is that you can delete/set the root level object.
function resolve(obj, path) {
let root = obj = [obj];
path = [0, ...path];
while (path.length > 1)
obj = obj[path.shift()];
return [obj, path[0], root];
}
Object.get = (obj, path) => {
let [parent, key] = resolve(obj, path);
return parent[key];
};
Object.del = (obj, path) => {
let [parent, key, root] = resolve(obj, path);
delete parent[key];
return root[0];
};
Object.set = (obj, path, value) => {
let [parent, key, root] = resolve(obj, path);
parent[key] = value;
return root[0];
};
Demo of other features:
The bob = for .set(/.del( isn't necessary unless your path might be empty (manipulating the root object).
I prove that I don't clone the object by using steve to keep a reference to the original and checking bob == steve //true after that first .set(
Just in case, anyone's visiting this question in 2017 or later and looking for an easy-to-remember way, here's an elaborate blog post on Accessing Nested Objects in JavaScript without being bamboozled by
Cannot read property 'foo' of undefined error
Access Nested Objects Using Array Reduce
Let's take this example structure
const user = {
id: 101,
email: 'jack#dev.com',
personalInfo: {
name: 'Jack',
address: [{
line1: 'westwish st',
line2: 'washmasher',
city: 'wallas',
state: 'WX'
}]
}
}
To be able to access nested arrays, you can write your own array reduce util.
const getNestedObject = (nestedObj, pathArr) => {
return pathArr.reduce((obj, key) =>
(obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);
}
// pass in your object structure as array elements
const name = getNestedObject(user, ['personalInfo', 'name']);
// to access nested array, just pass in array index as an element the path array.
const city = getNestedObject(user, ['personalInfo', 'address', 0, 'city']);
// this will return the city from the first address item.
There is also an excellent type handling minimal library typy that does all this for you.
With typy, your code will look like this
const city = t(user, 'personalInfo.address[0].city').safeObject;
Disclaimer: I am the author of this package.
Here I offer more ways, which seem faster in many respects:
Option 1: Split string on . or [ or ] or ' or ", reverse it, skip empty items.
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var parts = path.split(/\[|\]|\.|'|"/g).reverse(), name; // (why reverse? because it's usually faster to pop off the end of an array)
while (parts.length) { name=parts.pop(); if (name) origin=origin[name]; }
return origin;
}
Option 2 (fastest of all, except eval): Low level character scan (no regex/split/etc, just a quick char scan).
Note: This one does not support quotes for indexes.
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var c = '', pc, i = 0, n = path.length, name = '';
if (n) while (i<=n) ((c = path[i++]) == '.' || c == '[' || c == ']' || c == void 0) ? (name?(origin = origin[name], name = ''):(pc=='.'||pc=='['||pc==']'&&c==']'?i=n+2:void 0),pc=c) : name += c;
if (i==n+2) throw "Invalid path: "+path;
return origin;
} // (around 1,000,000+/- ops/sec)
Option 3: (new: option 2 expanded to support quotes - a bit slower, but still fast)
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var c, pc, i = 0, n = path.length, name = '', q;
while (i<=n)
((c = path[i++]) == '.' || c == '[' || c == ']' || c == "'" || c == '"' || c == void 0) ? (c==q&&path[i]==']'?q='':q?name+=c:name?(origin?origin=origin[name]:i=n+2,name='') : (pc=='['&&(c=='"'||c=="'")?q=c:pc=='.'||pc=='['||pc==']'&&c==']'||pc=='"'||pc=="'"?i=n+2:void 0), pc=c) : name += c;
if (i==n+2 || name) throw "Invalid path: "+path;
return origin;
}
JSPerf: http://jsperf.com/ways-to-dereference-a-delimited-property-string/3
"eval(...)" is still king though (performance wise that is). If you have property paths directly under your control, there shouldn't be any issues with using 'eval' (especially if speed is desired). If pulling property paths "over the wire" (on the line!? lol :P), then yes, use something else to be safe. Only an idiot would say to never use "eval" at all, as there ARE good reasons when to use it. Also, "It is used in Doug Crockford's JSON parser." If the input is safe, then no problems at all. Use the right tool for the right job, that's it.
AngularJS
Speigg's approach is very neat and clean, though I found this reply while searching for the solution of accessing AngularJS $scope properties by string path and with a little modification it does the job:
$scope.resolve = function( path, obj ) {
return path.split('.').reduce( function( prev, curr ) {
return prev[curr];
}, obj || this );
}
Just place this function in your root controller and use it any child scope like this:
$scope.resolve( 'path.to.any.object.in.scope')
If you want a solution that can properly detect and report details of any issue with the path parsing, I wrote my own solution to this - library path-value.
const {resolveValue} = require('path-value');
resolveValue(someObject, 'part1.name'); //=> Part 1
resolveValue(someObject, 'part2.qty'); //=> 50
resolveValue(someObject, 'part3.0.name'); //=> Part 3A
Note that for indexes we use .0, and not [0], because parsing the latter adds a performance penalty, while .0 works directly in JavaScript, and is thus very fast.
However, full ES5 JavaScript syntax is also supported, it just needs to be tokenized first:
const {resolveValue, tokenizePath} = require('path-value');
const path = tokenizePath('part3[0].name'); //=> ['part3', '0', 'name']
resolveValue(someObject, path); //=> Part 3A
/**
* Access a deep value inside a object
* Works by passing a path like "foo.bar", also works with nested arrays like "foo[0][1].baz"
* #author Victor B. https://gist.github.com/victornpb/4c7882c1b9d36292308e
* Unit tests: http://jsfiddle.net/Victornpb/0u1qygrh/
*/
function getDeepVal(obj, path) {
if (typeof obj === "undefined" || obj === null) return;
path = path.split(/[\.\[\]\"\']{1,2}/);
for (var i = 0, l = path.length; i < l; i++) {
if (path[i] === "") continue;
obj = obj[path[i]];
if (typeof obj === "undefined" || obj === null) return;
}
return obj;
}
Works with
getDeepVal(obj,'foo.bar')
getDeepVal(obj,'foo.1.bar')
getDeepVal(obj,'foo[0].baz')
getDeepVal(obj,'foo[1][2]')
getDeepVal(obj,"foo['bar'].baz")
getDeepVal(obj,"foo['bar']['baz']")
getDeepVal(obj,"foo.bar.0.baz[1]['2']['w'].aaa[\"f\"].bb")
I haven't yet found a package to do all of the operations with a string path, so I ended up writing my own quick little package which supports insert(), get() (with default return), set() and remove() operations.
You can use dot notation, brackets, number indices, string number properties, and keys with non-word characters. Simple usage below:
> var jsocrud = require('jsocrud');
...
// Get (Read) ---
> var obj = {
> foo: [
> {
> 'key w/ non-word chars': 'bar'
> }
> ]
> };
undefined
> jsocrud.get(obj, '.foo[0]["key w/ non-word chars"]');
'bar'
https://www.npmjs.com/package/jsocrud
https://github.com/vertical-knowledge/jsocrud
Simple function, allowing for either a string or array path.
function get(obj, path) {
if(typeof path === 'string') path = path.split('.');
if(path.length === 0) return obj;
return get(obj[path[0]], path.slice(1));
}
const obj = {a: {b: {c: 'foo'}}};
console.log(get(obj, 'a.b.c')); //foo
OR
console.log(get(obj, ['a', 'b', 'c'])); //foo
There is an npm module now for doing this: https://github.com/erictrinh/safe-access
Example usage:
var access = require('safe-access');
access(very, 'nested.property.and.array[0]');
While reduce is good, I am surprised no one used forEach:
function valueForKeyPath(obj, path){
const keys = path.split('.');
keys.forEach((key)=> obj = obj[key]);
return obj;
};
Test
I'm developing online-shop with React. I tried to change values in copied state object to update original state with it on submit.
Examples above haven't worked for me, because most of them mutate structure of copied object. I found working example of the function for accessing and changing values of the deep nested object properties: https://lowrey.me/create-an-object-by-path-in-javascript-2/ Here it is:
const createPath = (obj, path, value = null) => {
path = typeof path === 'string' ? path.split('.') : path;
let current = obj;
while (path.length > 1) {
const [head, ...tail] = path;
path = tail;
if (current[head] === undefined) {
current[head] = {};
}
current = current[head];
}
current[path[0]] = value;
return obj;
};
You can use ramda library.
Learning ramda also helps you to work with immutable objects easily.
var obj = {
a:{
b: {
c:[100,101,{
d: 1000
}]
}
}
};
var lens = R.lensPath('a.b.c.2.d'.split('.'));
var result = R.view(lens, obj);
https://codepen.io/ghominejad/pen/BayJZOQ
Based on Alnitak's answer.
I wrapped the polyfill in a check, and reduced the function to a single chained reduction.
if (Object.byPath === undefined) {
Object.byPath = (obj, path) => path
.replace(/\[(\w+)\]/g, '.$1')
.replace(/^\./, '')
.split(/\./g)
.reduce((ref, key) => key in ref ? ref[key] : ref, obj)
}
const data = {
foo: {
bar: [{
baz: 1
}]
}
}
console.log(Object.byPath(data, 'foo.bar[0].baz'))
This can be simplified by splitting the logic into three separate functions:
const isVal = a => a != null; // everything except undefined + null
const prop = prop => obj => {
if (isVal(obj)) {
const value = obj[prop];
if (isVal(value)) return value;
else return undefined;
} else return undefined;
};
const path = paths => obj => {
const pathList = typeof paths === 'string' ? paths.split('.') : paths;
return pathList.reduce((value, key) => prop(key)(value), obj);
};
//usage:
const myObject = { foo: { bar: { baz: 'taco' } } };
const result = path('foo.bar')(myObject);
//results => { baz: 'taco' }
This variation supports:
passing an array or string argument
dealing with undefined values during invocation and execution
testing each function independently
using each function independently
DotObject = obj => new Proxy(obj, {
get: function(o,k) {
const m = k.match(/(.+?)\.(.+)/)
return m ? this.get(o[m[1]], m[2]) : o[k]
}
})
const test = DotObject({a: {b: {c: 'wow'}}})
console.log(test['a.b.c'])
If you need to access different nested key without knowing it at coding time (it will be trivial to address them) you can use the array notation accessor:
var part1name = someObject['part1']['name'];
var part2quantity = someObject['part2']['qty'];
var part3name1 = someObject['part3'][0]['name'];
They are equivalent to the dot notation accessor and may vary at runtime, for example:
var part = 'part1';
var property = 'name';
var part1name = someObject[part][property];
is equivalent to
var part1name = someObject['part1']['name'];
or
var part1name = someObject.part1.name;
I hope this address your question...
EDIT
I won't use a string to mantain a sort of xpath query to access an object value.
As you have to call a function to parse the query and retrieve the value I would follow another path (not :
var part1name = function(){ return this.part1.name; }
var part2quantity = function() { return this['part2']['qty']; }
var part3name1 = function() { return this.part3[0]['name'];}
// usage: part1name.apply(someObject);
or, if you are uneasy with the apply method
var part1name = function(obj){ return obj.part1.name; }
var part2quantity = function(obj) { return obj['part2']['qty']; }
var part3name1 = function(obj) { return obj.part3[0]['name'];}
// usage: part1name(someObject);
The functions are shorter, clearer, the interpreter check them for you for syntax errors and so on.
By the way, I feel that a simple assignment made at right time will be sufficent...
Just had the same question recently and successfully used https://npmjs.org/package/tea-properties which also set nested object/arrays :
get:
var o = {
prop: {
arr: [
{foo: 'bar'}
]
}
};
var properties = require('tea-properties');
var value = properties.get(o, 'prop.arr[0].foo');
assert(value, 'bar'); // true
set:
var o = {};
var properties = require('tea-properties');
properties.set(o, 'prop.arr[0].foo', 'bar');
assert(o.prop.arr[0].foo, 'bar'); // true
Based on a previous answer, I have created a function that can also handle brackets. But no dots inside them due to the split.
function get(obj, str) {
return str.split(/\.|\[/g).map(function(crumb) {
return crumb.replace(/\]$/, '').trim().replace(/^(["'])((?:(?!\1)[^\\]|\\.)*?)\1$/, (match, quote, str) => str.replace(/\\(\\)?/g, "$1"));
}).reduce(function(obj, prop) {
return obj ? obj[prop] : undefined;
}, obj);
}
// (IE9+) Two steps
var pathString = "[0]['property'].others[3].next['final']";
var obj = [{
property: {
others: [1, 2, 3, {
next: {
final: "SUCCESS"
}
}]
}
}];
// Turn string to path array
var pathArray = pathString
.replace(/\[["']?([\w]+)["']?\]/g,".$1")
.split(".")
.splice(1);
// Add object prototype method
Object.prototype.path = function (path) {
try {
return [this].concat(path).reduce(function (f, l) {
return f[l];
});
} catch (e) {
console.error(e);
}
};
// usage
console.log(obj.path(pathArray));
console.log(obj.path([0,"doesNotExist"]));

Describe object path by string [duplicate]

I have a data structure like this :
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'
}
]
};
And I would like to access the data using these variable :
var part1name = "part1.name";
var part2quantity = "part2.qty";
var part3name1 = "part3[0].name";
part1name should be filled with someObject.part1.name 's value, which is "Part 1". Same thing with part2quantity which filled with 60.
Is there anyway to achieve this with either pure javascript or JQuery?
I just made this based on some similar code I already had, it appears to work:
Object.byString = function(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;
}
Usage::
Object.byString(someObj, 'part3[0].name');
See a working demo at http://jsfiddle.net/alnitak/hEsys/
EDIT some have noticed that this code will throw an error if passed a string where the left-most indexes don't correspond to a correctly nested entry within the object. This is a valid concern, but IMHO best addressed with a try / catch block when calling, rather than having this function silently return undefined for an invalid index.
This is now supported by lodash using _.get(obj, property). See https://lodash.com/docs#get
Example from the docs:
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// → 3
_.get(object, ['a', '0', 'b', 'c']);
// → 3
_.get(object, 'a.b.c', 'default');
// → 'default'
This is the solution I use:
function resolve(path, obj=self, separator='.') {
var properties = Array.isArray(path) ? path : path.split(separator)
return properties.reduce((prev, curr) => prev?.[curr], obj)
}
Example usage:
// accessing property path on global scope
resolve("document.body.style.width")
// or
resolve("style.width", document.body)
// accessing array indexes
// (someObject has been defined in the question)
resolve("part3.0.size", someObject) // returns '10'
// accessing non-existent properties
// returns undefined when intermediate properties are not defined:
resolve('properties.that.do.not.exist', {hello:'world'})
// accessing properties with unusual keys by changing the separator
var obj = { object: { 'a.property.name.with.periods': 42 } }
resolve('object->a.property.name.with.periods', obj, '->') // returns 42
// accessing properties with unusual keys by passing a property name array
resolve(['object', 'a.property.name.with.periods'], obj) // returns 42
Limitations:
Can't use brackets ([]) for array indices—though specifying array indices between the separator token (e.g., .) works fine as shown above.
ES6: Only one line in Vanila JS (it return null if don't find instead of giving error):
'path.string'.split('.').reduce((p,c)=>p&&p[c]||null, MyOBJ)
Or example:
'a.b.c'.split('.').reduce((p,c)=>p&&p[c]||null, {a:{b:{c:1}}})
With Optional chaining operator:
'a.b.c'.split('.').reduce((p,c)=>p?.[c], {a:{b:{c:1}}})
For a ready to use function that also recognizes false, 0 and negative number and accept default values as parameter:
const resolvePath = (object, path, defaultValue) => path
.split('.')
.reduce((o, p) => o ? o[p] : defaultValue, object)
Example to use:
resolvePath(window,'document.body') => <body>
resolvePath(window,'document.body.xyz') => undefined
resolvePath(window,'document.body.xyz', null) => null
resolvePath(window,'document.body.xyz', 1) => 1
Bonus:
To set a path (Requested by #rob-gordon) you can use:
const setPath = (object, path, value) => path
.split('.')
.reduce((o,p,i) => o[p] = path.split('.').length === ++i ? value : o[p] || {}, object)
Example:
let myVar = {}
setPath(myVar, 'a.b.c', 42) => 42
console.log(myVar) => {a: {b: {c: 42}}}
Access array with []:
const resolvePath = (object, path, defaultValue) => path
.split(/[\.\[\]\'\"]/)
.filter(p => p)
.reduce((o, p) => o ? o[p] : defaultValue, object)
Example:
const myVar = {a:{b:[{c:1}]}}
resolvePath(myVar,'a.b[0].c') => 1
resolvePath(myVar,'a["b"][\'0\'].c') => 1
You'd have to parse the string yourself:
function getProperty(obj, prop) {
var parts = prop.split('.');
if (Array.isArray(parts)) {
var last = parts.pop(),
l = parts.length,
i = 1,
current = parts[0];
while((obj = obj[current]) && i < l) {
current = parts[i];
i++;
}
if(obj) {
return obj[last];
}
} else {
throw 'parts is not valid array';
}
}
This required that you also define array indexes with dot notation:
var part3name1 = "part3.0.name";
It makes the parsing easier.
DEMO
Works for arrays / arrays inside the object also.
Defensive against invalid values.
/**
* Retrieve nested item from object/array
* #param {Object|Array} obj
* #param {String} path dot separated
* #param {*} def default value ( if result undefined )
* #returns {*}
*/
function path(obj, path, def){
var i, len;
for(i = 0,path = path.split('.'), len = path.length; i < len; i++){
if(!obj || typeof obj !== 'object') return def;
obj = obj[path[i]];
}
if(obj === undefined) return def;
return obj;
}
//////////////////////////
// TEST //
//////////////////////////
var arr = [true, {'sp ace': true}, true]
var obj = {
'sp ace': true,
arr: arr,
nested: {'dotted.str.ing': true},
arr3: arr
}
shouldThrow(`path(obj, "arr.0")`);
shouldBeDefined(`path(obj, "arr[0]")`);
shouldBeEqualToNumber(`path(obj, "arr.length")`, 3);
shouldBeTrue(`path(obj, "sp ace")`);
shouldBeEqualToString(`path(obj, "none.existed.prop", "fallback")`, "fallback");
shouldBeTrue(`path(obj, "nested['dotted.str.ing'])`);
<script src="https://cdn.rawgit.com/coderek/e7b30bac7634a50ad8fd/raw/174b6634c8f57aa8aac0716c5b7b2a7098e03584/js-test.js"></script>
This will probably never see the light of day... but here it is anyway.
Replace [] bracket syntax with .
Split on . character
Remove blank strings
Find the path (otherwise undefined)
(For finding a path to an object, use this pathTo solution.)
// "one liner" (ES6)
const deep_value = (obj, path) =>
path
.replace(/\[|\]\.?/g, '.')
.split('.')
.filter(s => s)
.reduce((acc, val) => acc && acc[val], obj);
// ... and that's 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'
}
// ...
],
'pa[rt3' : [
{
'name': 'Part 3A',
'size': '10',
'qty' : '20'
}
// ...
]
};
console.log(deep_value(someObject, "part1.name")); // Part 1
console.log(deep_value(someObject, "part2.qty")); // 60
console.log(deep_value(someObject, "part3[0].name")); // Part 3A
console.log(deep_value(someObject, "part3[0].....name")); // Part 3A - invalid blank paths removed
console.log(deep_value(someObject, "pa[rt3[0].name")); // undefined - name does not support square brackets
using eval:
var part1name = eval("someObject.part1.name");
wrap to return undefined on error
function path(obj, path) {
try {
return eval("obj." + path);
} catch(e) {
return undefined;
}
}
http://jsfiddle.net/shanimal/b3xTw/
Please use common sense and caution when wielding the power of eval. It's a bit like a light saber, if you turn it on there's a 90% chance you'll sever a limb. Its not for everybody.
You can manage to obtain the value of a deep object member with dot notation without any external JavaScript library with the following simple trick:
function objectGet(obj, path) { return new Function('_', 'return _.' + path)(obj); };
In your case to obtain value of part1.name from someObject just do:
objectGet(someObject, 'part1.name');
Here is a simple fiddle demo: https://jsfiddle.net/harishanchu/oq5esowf/
It's a one liner with lodash.
const deep = { l1: { l2: { l3: "Hello" } } };
const prop = "l1.l2.l3";
const val = _.reduce(prop.split('.'), function(result, value) { return result ? result[value] : undefined; }, deep);
// val === "Hello"
Or even better...
const val = _.get(deep, prop);
Or ES6 version w/ reduce...
const val = prop.split('.').reduce((r, val) => { return r ? r[val] : undefined; }, deep);
Plunkr
I think you are asking for this:
var part1name = someObject.part1.name;
var part2quantity = someObject.part2.qty;
var part3name1 = someObject.part3[0].name;
You could be asking for this:
var part1name = someObject["part1"]["name"];
var part2quantity = someObject["part2"]["qty"];
var part3name1 = someObject["part3"][0]["name"];
Both of which will work
Or maybe you are asking for this
var partName = "part1";
var nameStr = "name";
var part1name = someObject[partName][nameStr];
Finally you could be asking for this
var partName = "part1.name";
var partBits = partName.split(".");
var part1name = someObject[partBits[0]][partBits[1]];
Instead of trying to emulate JS syntax which you will have to spend a bunch of compute parsing, or just get wrong/forget things like a bunch of these answers (keys with .s in, anyone?), just use an array of keys.
var part1name = Object.get(someObject, ['part1', 'name']);
var part2quantity = Object.get(someObject, ['part2', 'qty']);
var part3name1 = Object.get(someObject, ['part3', 0, 'name']);
If you need to use a single string instead, simply JSONify it.
Another improvement in this method is that you can delete/set the root level object.
function resolve(obj, path) {
let root = obj = [obj];
path = [0, ...path];
while (path.length > 1)
obj = obj[path.shift()];
return [obj, path[0], root];
}
Object.get = (obj, path) => {
let [parent, key] = resolve(obj, path);
return parent[key];
};
Object.del = (obj, path) => {
let [parent, key, root] = resolve(obj, path);
delete parent[key];
return root[0];
};
Object.set = (obj, path, value) => {
let [parent, key, root] = resolve(obj, path);
parent[key] = value;
return root[0];
};
Demo of other features:
The bob = for .set(/.del( isn't necessary unless your path might be empty (manipulating the root object).
I prove that I don't clone the object by using steve to keep a reference to the original and checking bob == steve //true after that first .set(
Just in case, anyone's visiting this question in 2017 or later and looking for an easy-to-remember way, here's an elaborate blog post on Accessing Nested Objects in JavaScript without being bamboozled by
Cannot read property 'foo' of undefined error
Access Nested Objects Using Array Reduce
Let's take this example structure
const user = {
id: 101,
email: 'jack#dev.com',
personalInfo: {
name: 'Jack',
address: [{
line1: 'westwish st',
line2: 'washmasher',
city: 'wallas',
state: 'WX'
}]
}
}
To be able to access nested arrays, you can write your own array reduce util.
const getNestedObject = (nestedObj, pathArr) => {
return pathArr.reduce((obj, key) =>
(obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);
}
// pass in your object structure as array elements
const name = getNestedObject(user, ['personalInfo', 'name']);
// to access nested array, just pass in array index as an element the path array.
const city = getNestedObject(user, ['personalInfo', 'address', 0, 'city']);
// this will return the city from the first address item.
There is also an excellent type handling minimal library typy that does all this for you.
With typy, your code will look like this
const city = t(user, 'personalInfo.address[0].city').safeObject;
Disclaimer: I am the author of this package.
Here I offer more ways, which seem faster in many respects:
Option 1: Split string on . or [ or ] or ' or ", reverse it, skip empty items.
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var parts = path.split(/\[|\]|\.|'|"/g).reverse(), name; // (why reverse? because it's usually faster to pop off the end of an array)
while (parts.length) { name=parts.pop(); if (name) origin=origin[name]; }
return origin;
}
Option 2 (fastest of all, except eval): Low level character scan (no regex/split/etc, just a quick char scan).
Note: This one does not support quotes for indexes.
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var c = '', pc, i = 0, n = path.length, name = '';
if (n) while (i<=n) ((c = path[i++]) == '.' || c == '[' || c == ']' || c == void 0) ? (name?(origin = origin[name], name = ''):(pc=='.'||pc=='['||pc==']'&&c==']'?i=n+2:void 0),pc=c) : name += c;
if (i==n+2) throw "Invalid path: "+path;
return origin;
} // (around 1,000,000+/- ops/sec)
Option 3: (new: option 2 expanded to support quotes - a bit slower, but still fast)
function getValue(path, origin) {
if (origin === void 0 || origin === null) origin = self ? self : this;
if (typeof path !== 'string') path = '' + path;
var c, pc, i = 0, n = path.length, name = '', q;
while (i<=n)
((c = path[i++]) == '.' || c == '[' || c == ']' || c == "'" || c == '"' || c == void 0) ? (c==q&&path[i]==']'?q='':q?name+=c:name?(origin?origin=origin[name]:i=n+2,name='') : (pc=='['&&(c=='"'||c=="'")?q=c:pc=='.'||pc=='['||pc==']'&&c==']'||pc=='"'||pc=="'"?i=n+2:void 0), pc=c) : name += c;
if (i==n+2 || name) throw "Invalid path: "+path;
return origin;
}
JSPerf: http://jsperf.com/ways-to-dereference-a-delimited-property-string/3
"eval(...)" is still king though (performance wise that is). If you have property paths directly under your control, there shouldn't be any issues with using 'eval' (especially if speed is desired). If pulling property paths "over the wire" (on the line!? lol :P), then yes, use something else to be safe. Only an idiot would say to never use "eval" at all, as there ARE good reasons when to use it. Also, "It is used in Doug Crockford's JSON parser." If the input is safe, then no problems at all. Use the right tool for the right job, that's it.
AngularJS
Speigg's approach is very neat and clean, though I found this reply while searching for the solution of accessing AngularJS $scope properties by string path and with a little modification it does the job:
$scope.resolve = function( path, obj ) {
return path.split('.').reduce( function( prev, curr ) {
return prev[curr];
}, obj || this );
}
Just place this function in your root controller and use it any child scope like this:
$scope.resolve( 'path.to.any.object.in.scope')
If you want a solution that can properly detect and report details of any issue with the path parsing, I wrote my own solution to this - library path-value.
const {resolveValue} = require('path-value');
resolveValue(someObject, 'part1.name'); //=> Part 1
resolveValue(someObject, 'part2.qty'); //=> 50
resolveValue(someObject, 'part3.0.name'); //=> Part 3A
Note that for indexes we use .0, and not [0], because parsing the latter adds a performance penalty, while .0 works directly in JavaScript, and is thus very fast.
However, full ES5 JavaScript syntax is also supported, it just needs to be tokenized first:
const {resolveValue, tokenizePath} = require('path-value');
const path = tokenizePath('part3[0].name'); //=> ['part3', '0', 'name']
resolveValue(someObject, path); //=> Part 3A
/**
* Access a deep value inside a object
* Works by passing a path like "foo.bar", also works with nested arrays like "foo[0][1].baz"
* #author Victor B. https://gist.github.com/victornpb/4c7882c1b9d36292308e
* Unit tests: http://jsfiddle.net/Victornpb/0u1qygrh/
*/
function getDeepVal(obj, path) {
if (typeof obj === "undefined" || obj === null) return;
path = path.split(/[\.\[\]\"\']{1,2}/);
for (var i = 0, l = path.length; i < l; i++) {
if (path[i] === "") continue;
obj = obj[path[i]];
if (typeof obj === "undefined" || obj === null) return;
}
return obj;
}
Works with
getDeepVal(obj,'foo.bar')
getDeepVal(obj,'foo.1.bar')
getDeepVal(obj,'foo[0].baz')
getDeepVal(obj,'foo[1][2]')
getDeepVal(obj,"foo['bar'].baz")
getDeepVal(obj,"foo['bar']['baz']")
getDeepVal(obj,"foo.bar.0.baz[1]['2']['w'].aaa[\"f\"].bb")
I haven't yet found a package to do all of the operations with a string path, so I ended up writing my own quick little package which supports insert(), get() (with default return), set() and remove() operations.
You can use dot notation, brackets, number indices, string number properties, and keys with non-word characters. Simple usage below:
> var jsocrud = require('jsocrud');
...
// Get (Read) ---
> var obj = {
> foo: [
> {
> 'key w/ non-word chars': 'bar'
> }
> ]
> };
undefined
> jsocrud.get(obj, '.foo[0]["key w/ non-word chars"]');
'bar'
https://www.npmjs.com/package/jsocrud
https://github.com/vertical-knowledge/jsocrud
Simple function, allowing for either a string or array path.
function get(obj, path) {
if(typeof path === 'string') path = path.split('.');
if(path.length === 0) return obj;
return get(obj[path[0]], path.slice(1));
}
const obj = {a: {b: {c: 'foo'}}};
console.log(get(obj, 'a.b.c')); //foo
OR
console.log(get(obj, ['a', 'b', 'c'])); //foo
There is an npm module now for doing this: https://github.com/erictrinh/safe-access
Example usage:
var access = require('safe-access');
access(very, 'nested.property.and.array[0]');
While reduce is good, I am surprised no one used forEach:
function valueForKeyPath(obj, path){
const keys = path.split('.');
keys.forEach((key)=> obj = obj[key]);
return obj;
};
Test
I'm developing online-shop with React. I tried to change values in copied state object to update original state with it on submit.
Examples above haven't worked for me, because most of them mutate structure of copied object. I found working example of the function for accessing and changing values of the deep nested object properties: https://lowrey.me/create-an-object-by-path-in-javascript-2/ Here it is:
const createPath = (obj, path, value = null) => {
path = typeof path === 'string' ? path.split('.') : path;
let current = obj;
while (path.length > 1) {
const [head, ...tail] = path;
path = tail;
if (current[head] === undefined) {
current[head] = {};
}
current = current[head];
}
current[path[0]] = value;
return obj;
};
You can use ramda library.
Learning ramda also helps you to work with immutable objects easily.
var obj = {
a:{
b: {
c:[100,101,{
d: 1000
}]
}
}
};
var lens = R.lensPath('a.b.c.2.d'.split('.'));
var result = R.view(lens, obj);
https://codepen.io/ghominejad/pen/BayJZOQ
Based on Alnitak's answer.
I wrapped the polyfill in a check, and reduced the function to a single chained reduction.
if (Object.byPath === undefined) {
Object.byPath = (obj, path) => path
.replace(/\[(\w+)\]/g, '.$1')
.replace(/^\./, '')
.split(/\./g)
.reduce((ref, key) => key in ref ? ref[key] : ref, obj)
}
const data = {
foo: {
bar: [{
baz: 1
}]
}
}
console.log(Object.byPath(data, 'foo.bar[0].baz'))
This can be simplified by splitting the logic into three separate functions:
const isVal = a => a != null; // everything except undefined + null
const prop = prop => obj => {
if (isVal(obj)) {
const value = obj[prop];
if (isVal(value)) return value;
else return undefined;
} else return undefined;
};
const path = paths => obj => {
const pathList = typeof paths === 'string' ? paths.split('.') : paths;
return pathList.reduce((value, key) => prop(key)(value), obj);
};
//usage:
const myObject = { foo: { bar: { baz: 'taco' } } };
const result = path('foo.bar')(myObject);
//results => { baz: 'taco' }
This variation supports:
passing an array or string argument
dealing with undefined values during invocation and execution
testing each function independently
using each function independently
DotObject = obj => new Proxy(obj, {
get: function(o,k) {
const m = k.match(/(.+?)\.(.+)/)
return m ? this.get(o[m[1]], m[2]) : o[k]
}
})
const test = DotObject({a: {b: {c: 'wow'}}})
console.log(test['a.b.c'])
If you need to access different nested key without knowing it at coding time (it will be trivial to address them) you can use the array notation accessor:
var part1name = someObject['part1']['name'];
var part2quantity = someObject['part2']['qty'];
var part3name1 = someObject['part3'][0]['name'];
They are equivalent to the dot notation accessor and may vary at runtime, for example:
var part = 'part1';
var property = 'name';
var part1name = someObject[part][property];
is equivalent to
var part1name = someObject['part1']['name'];
or
var part1name = someObject.part1.name;
I hope this address your question...
EDIT
I won't use a string to mantain a sort of xpath query to access an object value.
As you have to call a function to parse the query and retrieve the value I would follow another path (not :
var part1name = function(){ return this.part1.name; }
var part2quantity = function() { return this['part2']['qty']; }
var part3name1 = function() { return this.part3[0]['name'];}
// usage: part1name.apply(someObject);
or, if you are uneasy with the apply method
var part1name = function(obj){ return obj.part1.name; }
var part2quantity = function(obj) { return obj['part2']['qty']; }
var part3name1 = function(obj) { return obj.part3[0]['name'];}
// usage: part1name(someObject);
The functions are shorter, clearer, the interpreter check them for you for syntax errors and so on.
By the way, I feel that a simple assignment made at right time will be sufficent...
Just had the same question recently and successfully used https://npmjs.org/package/tea-properties which also set nested object/arrays :
get:
var o = {
prop: {
arr: [
{foo: 'bar'}
]
}
};
var properties = require('tea-properties');
var value = properties.get(o, 'prop.arr[0].foo');
assert(value, 'bar'); // true
set:
var o = {};
var properties = require('tea-properties');
properties.set(o, 'prop.arr[0].foo', 'bar');
assert(o.prop.arr[0].foo, 'bar'); // true
Based on a previous answer, I have created a function that can also handle brackets. But no dots inside them due to the split.
function get(obj, str) {
return str.split(/\.|\[/g).map(function(crumb) {
return crumb.replace(/\]$/, '').trim().replace(/^(["'])((?:(?!\1)[^\\]|\\.)*?)\1$/, (match, quote, str) => str.replace(/\\(\\)?/g, "$1"));
}).reduce(function(obj, prop) {
return obj ? obj[prop] : undefined;
}, obj);
}
// (IE9+) Two steps
var pathString = "[0]['property'].others[3].next['final']";
var obj = [{
property: {
others: [1, 2, 3, {
next: {
final: "SUCCESS"
}
}]
}
}];
// Turn string to path array
var pathArray = pathString
.replace(/\[["']?([\w]+)["']?\]/g,".$1")
.split(".")
.splice(1);
// Add object prototype method
Object.prototype.path = function (path) {
try {
return [this].concat(path).reduce(function (f, l) {
return f[l];
});
} catch (e) {
console.error(e);
}
};
// usage
console.log(obj.path(pathArray));
console.log(obj.path([0,"doesNotExist"]));

Categories

Resources