Given a JS object
var obj = { a: { b: '1', c: '2' } }
and a string
"a.b"
how can I convert the string to dot notation so I can go
var val = obj.a.b
If the string was just 'a'
, I could use obj[a]
. But this is more complex. I imagine there is some straightforward method but it escapes at present.
This question is related to
javascript
At the risk of beating a dead horse... I find this most useful in traversing nested objects to reference where you're at with respect to the base object or to a similar object with the same structure. To that end, this is useful with a nested object traversal function. Note that I've used an array to hold the path. It would be trivial to modify this to use either a string path or an array. Also note that you can assign "undefined" to the value, unlike some of the other implementations.
/*_x000D_
* Traverse each key in a nested object and call fn(curObject, key, value, baseObject, path)_x000D_
* on each. The path is an array of the keys required to get to curObject from_x000D_
* baseObject using objectPath(). If the call to fn() returns falsey, objects below_x000D_
* curObject are not traversed. Should be called as objectTaverse(baseObject, fn)._x000D_
* The third and fourth arguments are only used by recursion._x000D_
*/_x000D_
function objectTraverse (o, fn, base, path) {_x000D_
path = path || [];_x000D_
base = base || o;_x000D_
Object.keys(o).forEach(function (key) {_x000D_
if (fn(o, key, o[key], base, path) && jQuery.isPlainObject(o[key])) {_x000D_
path.push(key);_x000D_
objectTraverse(o[key], fn, base, path);_x000D_
path.pop();_x000D_
}_x000D_
});_x000D_
}_x000D_
_x000D_
/*_x000D_
* Get/set a nested key in an object. Path is an array of the keys to reference each level_x000D_
* of nesting. If value is provided, the nested key is set._x000D_
* The value of the nested key is returned._x000D_
*/_x000D_
function objectPath (o, path, value) {_x000D_
var last = path.pop();_x000D_
_x000D_
while (path.length && o) {_x000D_
o = o[path.shift()];_x000D_
}_x000D_
if (arguments.length < 3) {_x000D_
return (o? o[last] : o);_x000D_
}_x000D_
return (o[last] = value);_x000D_
}
_x000D_
I have extended the elegant answer by ninjagecko so that the function handles both dotted and/or array style references, and so that an empty string causes the parent object to be returned.
Here you go:
string_to_ref = function (object, reference) {
function arr_deref(o, ref, i) { return !ref ? o : (o[ref.slice(0, i ? -1 : ref.length)]) }
function dot_deref(o, ref) { return ref.split('[').reduce(arr_deref, o); }
return !reference ? object : reference.split('.').reduce(dot_deref, object);
};
See my working jsFiddle example here: http://jsfiddle.net/sc0ttyd/q7zyd/
This is my extended solution proposed by: ninjagecko
For me simple string notation was not enough, so below version supports things like:
index(obj, 'data.accounts[0].address[0].postcode');
/**
* Get object by index
* @supported
* - arrays supported
* - array indexes supported
* @not-supported
* - multiple arrays
* @issues:
* index(myAccount, 'accounts[0].address[0].id') - works fine
* index(myAccount, 'accounts[].address[0].id') - doesnt work
* @Example:
* index(obj, 'data.accounts[].id') => returns array of id's
* index(obj, 'data.accounts[0].id') => returns id of 0 element from array
* index(obj, 'data.accounts[0].addresses.list[0].id') => error
* @param obj
* @param path
* @returns {any}
*/
var index = function(obj, path, isArray?, arrIndex?){
// is an array
if(typeof isArray === 'undefined') isArray = false;
// array index,
// if null, will take all indexes
if(typeof arrIndex === 'undefined') arrIndex = null;
var _arrIndex = null;
var reduceArrayTag = function(i, subArrIndex){
return i.replace(/(\[)([\d]{0,})(\])/, (i) => {
var tmp = i.match(/(\[)([\d]{0,})(\])/);
isArray = true;
if(subArrIndex){
_arrIndex = (tmp[2] !== '') ? tmp[2] : null;
}else{
arrIndex = (tmp[2] !== '') ? tmp[2] : null;
}
return '';
});
}
function byIndex(obj, i) {
// if is an array
if(isArray){
isArray = false;
i = reduceArrayTag(i, true);
// if array index is null,
// return an array of with values from every index
if(!arrIndex){
var arrValues = [];
_.forEach(obj, (el) => {
arrValues.push(index(el, i, isArray, arrIndex));
})
return arrValues;
}
// if array index is specified
var value = obj[arrIndex][i];
if(isArray){
arrIndex = _arrIndex;
}else{
arrIndex = null;
}
return value;
}else{
// remove [] from notation,
// if [] has been removed, check the index of array
i = reduceArrayTag(i, false);
return obj[i]
}
}
// reduce with byIndex method
return path.split('.').reduce(byIndex, obj)
}
var a = { b: { c: 9 } };
function value(layer, path, value) {
var i = 0,
path = path.split('.');
for (; i < path.length; i++)
if (value != null && i + 1 === path.length)
layer[path[i]] = value;
layer = layer[path[i]];
return layer;
};
value(a, 'b.c'); // 9
value(a, 'b.c', 4);
value(a, 'b.c'); // 4
This is a lot of code when compared to the much simpler eval
way of doing it, but like Simon Willison says, you should never use eval.
Also, JSFiddle.
If you wish to convert any object that contains dot notation keys into an arrayed version of those keys you can use this.
This will convert something like
{
name: 'Andy',
brothers.0: 'Bob'
brothers.1: 'Steve'
brothers.2: 'Jack'
sisters.0: 'Sally'
}
to
{
name: 'Andy',
brothers: ['Bob', 'Steve', 'Jack']
sisters: ['Sally']
}
convertDotNotationToArray(objectWithDotNotation) {
Object.entries(objectWithDotNotation).forEach(([key, val]) => {
// Is the key of dot notation
if (key.includes('.')) {
const [name, index] = key.split('.');
// If you have not created an array version, create one
if (!objectWithDotNotation[name]) {
objectWithDotNotation[name] = new Array();
}
// Save the value in the newly created array at the specific index
objectWithDotNotation[name][index] = val;
// Delete the current dot notation key val
delete objectWithDotNotation[key];
}
});
}
Other proposals are a little cryptic, so I thought I'd contribute:
Object.prop = function(obj, prop, val){
var props = prop.split('.')
, final = props.pop(), p
while(p = props.shift()){
if (typeof obj[p] === 'undefined')
return undefined;
obj = obj[p]
}
return val ? (obj[final] = val) : obj[final]
}
var obj = { a: { b: '1', c: '2' } }
// get
console.log(Object.prop(obj, 'a.c')) // -> 2
// set
Object.prop(obj, 'a.c', function(){})
console.log(obj) // -> { a: { b: '1', c: [Function] } }
GET / SET answer that also works in react native (you can't assign to Object.prototype
currently):
Object.defineProperty(Object.prototype, 'getNestedProp', {
value: function(desc) {
var obj = this;
var arr = desc.split(".");
while(arr.length && (obj = obj[arr.shift()]));
return obj;
},
enumerable: false
});
Object.defineProperty(Object.prototype, 'setNestedProp', {
value: function(desc, value) {
var obj = this;
var arr = desc.split(".");
var last = arr.pop();
while(arr.length && (obj = obj[arr.shift()]));
obj[last] = value;
},
enumerable: false
});
Usage:
var a = { values: [{ value: null }] };
var b = { one: { two: 'foo' } };
a.setNestedProp('values.0.value', b.getNestedProp('one.two'));
console.log(a.values[0].value); // foo
you could also use lodash.get
You just install this package (npm i --save lodash.get) and then use it like this:
const get = require('lodash.get');
const myObj = { user: { firstName: 'Stacky', lastName: 'Overflowy' }, id: 123 };
console.log(get(myObj, 'user.firstName')); // prints Stacky
console.log(get(myObj, 'id')); //prints 123
//You can also update values
get(myObj, 'user').firstName = John;
Yes, it was asked 4 years ago and yes, extending base prototypes is not usually good idea but, if you keep all extensions in one place, they might be useful.
So, here is my way to do this.
Object.defineProperty(Object.prototype, "getNestedProperty", {
value : function (propertyName) {
var result = this;
var arr = propertyName.split(".");
while (arr.length && result) {
result = result[arr.shift()];
}
return result;
},
enumerable: false
});
Now you will be able to get nested property everywhere without importing module with function or copy/pasting function.
UPD.Example:
{a:{b:11}}.getNestedProperty('a.b'); //returns 11
UPD 2. Next extension brokes mongoose in my project. Also I've read that it might broke jquery. So, never do it in next way
Object.prototype.getNestedProperty = function (propertyName) {
var result = this;
var arr = propertyName.split(".");
while (arr.length && result) {
result = result[arr.shift()];
}
return result;
};
A little more involved example with recursion.
function recompose(obj,string){
var parts = string.split('.');
var newObj = obj[parts[0]];
if(parts[1]){
parts.splice(0,1);
var newString = parts.join('.');
return recompose(newObj,newString);
}
return newObj;
}
var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};
alert(recompose(obj,'a.d.a.b')); //blah
Few years later, I found this that handles scope and array. e.g. a['b']["c"].d.etc
function getScopedObj(scope, str) {
let obj=scope, arr;
try {
arr = str.split(/[\[\]\.]/) // split by [,],.
.filter(el => el) // filter out empty one
.map(el => el.replace(/^['"]+|['"]+$/g, '')); // remove string quotation
arr.forEach(el => obj = obj[el])
} catch(e) {
obj = undefined;
}
return obj;
}
window.a = {b: {c: {d: {etc: 'success'}}}}
getScopedObj(window, `a.b.c.d.etc`) // success
getScopedObj(window, `a['b']["c"].d.etc`) // success
getScopedObj(window, `a['INVALID']["c"].d.etc`) // undefined
It's not clear what your question is. Given your object, obj.a.b
would give you "2" just as it is. If you wanted to manipulate the string to use brackets, you could do this:
var s = 'a.b';
s = 'obj["' + s.replace(/\./g, '"]["') + '"]';
alert(s); // displays obj["a"]["b"]
I copied the following from Ricardo Tomasi's answer and modified to also create sub-objects that don't yet exist as necessary. It's a little less efficient (more if
s and creating of empty objects), but should be pretty good.
Also, it'll allow us to do Object.prop(obj, 'a.b', false)
where we couldn't before. Unfortunately, it still won't let us assign undefined
...Not sure how to go about that one yet.
/**
* Object.prop()
*
* Allows dot-notation access to object properties for both getting and setting.
*
* @param {Object} obj The object we're getting from or setting
* @param {string} prop The dot-notated string defining the property location
* @param {mixed} val For setting only; the value to set
*/
Object.prop = function(obj, prop, val){
var props = prop.split('.'),
final = props.pop(),
p;
for (var i = 0; i < props.length; i++) {
p = props[i];
if (typeof obj[p] === 'undefined') {
// If we're setting
if (typeof val !== 'undefined') {
// If we're not at the end of the props, keep adding new empty objects
if (i != props.length)
obj[p] = {};
}
else
return undefined;
}
obj = obj[p]
}
return typeof val !== "undefined" ? (obj[final] = val) : obj[final]
}
Here is my implementation
Implementation 1
Object.prototype.access = function() {
var ele = this[arguments[0]];
if(arguments.length === 1) return ele;
return ele.access.apply(ele, [].slice.call(arguments, 1));
}
Implementation 2 (using array reduce instead of slice)
Object.prototype.access = function() {
var self = this;
return [].reduce.call(arguments,function(prev,cur) {
return prev[cur];
}, self);
}
Examples:
var myobj = {'a':{'b':{'c':{'d':'abcd','e':[11,22,33]}}}};
myobj.access('a','b','c'); // returns: {'d':'abcd', e:[0,1,2,3]}
myobj.a.b.access('c','d'); // returns: 'abcd'
myobj.access('a','b','c','e',0); // returns: 11
it can also handle objects inside arrays as for
var myobj2 = {'a': {'b':[{'c':'ab0c'},{'d':'ab1d'}]}}
myobj2.access('a','b','1','d'); // returns: 'ab1d'
Here is my code without using eval
. Its easy to understand too.
function value(obj, props) {
if (!props) return obj;
var propsArr = props.split('.');
var prop = propsArr.splice(0, 1);
return value(obj[prop], propsArr.join('.'));
}
var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};
console.log(value(obj, 'a.d.a.b')); //returns blah
If you want to convert a string dot notation into an object, I've made a handy little helper than can turn a string like a.b.c.d
with a value of e
with dotPathToObject("a.b.c.d", "value")
returning this:
{
"a": {
"b": {
"c": {
"d": "value"
}
}
}
}
https://gist.github.com/ahallora/9731d73efb15bd3d3db647efa3389c12
You can obtain value of an object member by dot notation with a single line of code:
new Function('_', 'return _.' + path)(obj);
In you case:
var obj = { a: { b: '1', c: '2' } }
var val = new Function('_', 'return _.a.b')(obj);
To make it simple you may write a function like this:
function objGet(obj, path){
return new Function('_', 'return _.' + path)(obj);
}
Explanation:
The Function constructor creates a new Function object. In JavaScript every function is actually a Function object. Syntax to create a function explicitly with Function constructor is:
new Function ([arg1[, arg2[, ...argN]],] functionBody)
where arguments(arg1 to argN)
must be a string that corresponds to a valid javaScript identifier and functionBody
is a string containing the javaScript statements comprising the function definition.
In our case we take the advantage of string function body to retrieve object member with dot notation.
Hope it helps.
Using object-scan seems a bit overkill, but you can simply do
// const objectScan = require('object-scan');
const get = (obj, p) => objectScan([p], { abort: true, rtn: 'value' })(obj);
const obj = { a: { b: '1', c: '2' } };
console.log(get(obj, 'a.b'));
// => 1
console.log(get(obj, '*.c'));
// => 2
_x000D_
.as-console-wrapper {max-height: 100% !important; top: 0}
_x000D_
<script src="https://bundle.run/[email protected]"></script>
_x000D_
Disclaimer: I'm the author of object-scan
There are a lot more advanced examples in the readme.
If you expect to dereference the same path many times, building a function for each dot notation path actually has the best performance by far (expanding on the perf tests James Wilkins linked to in comments above).
var path = 'a.b.x';
var getter = new Function("obj", "return obj." + path + ";");
getter(obj);
Using the Function constructor has some of the same drawbacks as eval() in terms of security and worst-case performance, but IMO it's a badly underused tool for cases where you need a combination of extreme dynamism and high performance. I use this methodology to build array filter functions and call them inside an AngularJS digest loop. My profiles consistently show the array.filter() step taking less than 1ms to dereference and filter about 2000 complex objects, using dynamically-defined paths 3-4 levels deep.
A similar methodology could be used to create setter functions, of course:
var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");
You can use the library available at npm, which simplifies this process. https://www.npmjs.com/package/dot-object
var dot = require('dot-object');
var obj = {
some: {
nested: {
value: 'Hi there!'
}
}
};
var val = dot.pick('some.nested.value', obj);
console.log(val);
// Result: Hi there!
Many years since the original post. Now there is a great library called 'object-path'. https://github.com/mariocasciaro/object-path
Available on NPM and BOWER https://www.npmjs.com/package/object-path
It's as easy as:
objectPath.get(obj, "a.c.1"); //returns "f"
objectPath.set(obj, "a.j.0.f", "m");
And works for deeply nested properties and arrays.
If you can use lodash, there is a function, which does exactly that:
_.get(object, path, [defaultValue])
var val = _.get(obj, "a.b");
If you want to do this in the fastest possible way, while at the same time handling any issues with the path parsing or property resolution, check out path-value.
const {resolveValue} = require('path-value');
const value = resolveValue(obj, 'a.b.c');
The library is 100% TypeScript, works in NodeJS + all web browsers. And it is fully extendible, you can use lower-level resolvePath
, and handle errors your own way, if you want.
const {resolvePath} = require('path-value');
const res = resolvePath(obj, 'a.b.c'); //=> low-level parsing result descriptor
var find = function(root, path) {
var segments = path.split('.'),
cursor = root,
target;
for (var i = 0; i < segments.length; ++i) {
target = cursor[segments[i]];
if (typeof target == "undefined") return void 0;
cursor = target;
}
return cursor;
};
var obj = { a: { b: '1', c: '2' } }
find(obj, "a.b"); // 1
var set = function (root, path, value) {
var segments = path.split('.'),
cursor = root,
target;
for (var i = 0; i < segments.length - 1; ++i) {
cursor = cursor[segments[i]] || { };
}
cursor[segments[segments.length - 1]] = value;
};
set(obj, "a.k", function () { console.log("hello world"); });
find(obj, "a.k")(); // hello world
I used this code in my project
const getValue = (obj, arrPath) => (
arrPath.reduce((x, y) => {
if (y in x) return x[y]
return {}
}, obj)
)
Usage:
const obj = { id: { user: { local: 104 } } }
const path = [ 'id', 'user', 'local' ]
getValue(obj, path) // return 104
I suggest to split the path and iterate it and reduce the object you have. This proposal works with a default value for missing properties.
const getValue = (object, keys) => keys.split('.').reduce((o, k) => (o || {})[k], object);_x000D_
_x000D_
console.log(getValue({ a: { b: '1', c: '2' } }, 'a.b'));_x000D_
console.log(getValue({ a: { b: '1', c: '2' } }, 'foo.bar.baz'));
_x000D_
using Array Reduce function will get/set based on path provided.
i tested it with a.b.c
and a.b.2.c {a:{b:[0,1,{c:7}]}}
and its works for both getting key or mutating object to set value
cheerz
function setOrGet(obj, path=[], newValue){
const l = typeof path === 'string' ? path.split('.') : path;
return l.reduce((carry,item, idx)=>{
const leaf = carry[item];
// is this last item in path ? cool lets set/get value
if( l.length-idx===1) {
// mutate object if newValue is set;
carry[item] = newValue===undefined ? leaf : newValue;
// return value if its a get/object if it was a set
return newValue===undefined ? leaf : obj ;
}
carry[item] = leaf || {}; // mutate if key not an object;
return carry[item]; // return object ref: to continue reduction;
}, obj)
}
console.log(
setOrGet({a: {b:1}},'a.b') === 1 ||
'Test Case: Direct read failed'
)
console.log(
setOrGet({a: {b:1}},'a.c',22).a.c===22 ||
'Test Case: Direct set failed'
)
console.log(
setOrGet({a: {b:[1,2]}},'a.b.1',22).a.b[1]===22 ||
'Test Case: Direct set on array failed'
)
console.log(
setOrGet({a: {b:{c: {e:1} }}},'a.b.c.e',22).a.b.c. e===22 ||
'Test Case: deep get failed'
)
// failed !. Thats your homework :)
console.log(
setOrGet({a: {b:{c: {e:[1,2,3,4,5]} }}},'a.b.c.e.3 ',22)
)
_x000D_
do not use such a thing unless there is no other way!
i saw many examples people use it for translations for example from json; so you see function like locale('app.homepage.welcome')
. this is just bad. if you already have data in an object/json; and you know path.. then just use it directly example locale().app.homepage.welcome
by changing you function to return object you get typesafe, with autocomplete, less prone to typo's ..
Source: Stackoverflow.com