[javascript] Convert JavaScript string in dot notation into an object reference

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

The answer is


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_
_x000D_
/*_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_
_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;
};

Note if you're already using Lodash you can use the property or get functions:

var obj = { a: { b: '1', c: '2' } };
_.property('a.b')(obj); // => 1
_.get(obj, 'a.b'); // => 1

Underscore also has a property function but it doesn't support dot notation.


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 ifs 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

_x000D_
_x000D_
// 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_
_x000D_
_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.

_x000D_
_x000D_
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_
_x000D_
_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

_x000D_
_x000D_
    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_
_x000D_
_x000D_

my personal recommendation.

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 ..