'Convert a JavaScript string in dot notation into an object reference

Given a JavaScript 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 me at present.



Solution 1:[1]

If you can use Lodash, there is a function, which does exactly that:

_.get(object, path, [defaultValue])

var val = _.get(obj, "a.b");

Solution 2:[2]

You could use lodash.get

After installing (npm i lodash.get), use it like this:

const get = require('lodash.get');

const myObj = { 
    user: { 
        firstName: 'Stacky', 
        lastName: 'Overflowy',
        list: ['zero', 'one', 'two']
    }, 
    id: 123 
};

console.log(get(myObj, 'user.firstName')); // outputs Stacky
console.log(get(myObj, 'id'));             // outputs 123
console.log(get(myObj, 'user.list[1]'));   // outputs one

// You can also update values
get(myObj, 'user').firstName = 'John';

Solution 3:[3]

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'}}}};
console.log(recompose(obj, 'a.d.a.b')); //blah

Solution 4:[4]

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);

console.log(getValue({ a: { b: '1', c: '2' } }, 'a.b'));
console.log(getValue({ a: { b: '1', c: '2' } }, 'foo.bar.baz'));

Solution 5:[5]

2021

You don't need to pull in another dependency every time you wish for new capabilities in your program. Modern JS is very capable and the optional-chaining operator ?. is now widely supported and makes this kind of task easy as heck.

With a single line of code we can write get that takes an input object, t and string path. It works for object and arrays of any nesting level -

const get = (t, path) =>
  path.split(".").reduce((r, k) => r?.[k], t)
  
const mydata =
  { a: { b: [ 0, { c: { d: [ "hello", "world" ] } } ] } }

console.log(get(mydata, "a.b.1.c.d.0"))
console.log(get(mydata, "a.b.1.c.d.1"))
console.log(get(mydata, "a.b.x.y.z"))
"hello"
"world"
undefined

Solution 6:[6]

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");

Solution 7:[7]

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.

Solution 8:[8]

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] } }

Solution 9:[9]

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.

Solution 10:[10]

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!

Solution 11:[11]

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.js also has a property function, but it doesn't support dot notation.

Solution 12:[12]

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/

Solution 13:[13]

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.

Solution 14:[14]

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

Solution 15:[15]

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

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

Solution 16:[16]

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]
 }

Solution 17:[17]

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

Solution 18:[18]

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];
      }
    });

}

Solution 19:[19]

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

Solution 20:[20]

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'

Solution 21:[21]

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

Solution 22:[22]

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
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/[email protected]"></script>

Disclaimer: I'm the author of object-scan

There are a lot more advanced examples in the readme.

Solution 23:[23]

This is one of those cases, where you ask 10 developers and you get 10 answers.

Below is my [simplified] solution for OP, using dynamic programming.

The idea is that you would pass an existing DTO object that you wish to UPDATE. This makes the method most useful in the case where you have a form with several input elements having name attributes set with dot (fluent) syntax.

Example use:

<input type="text" name="person.contact.firstName" />

Code snippet:

const setFluently = (obj, path, value) => {
  if (typeof path === "string") {
    return setFluently(obj, path.split("."), value);
  }

  if (path.length <= 1) {
    obj[path[0]] = value;
    return obj;
  }

  const key = path[0];
  obj[key] = setFluently(obj[key] ? obj[key] : {}, path.slice(1), value);
  return obj;
};

const origObj = {
  a: {
    b: "1",
    c: "2"
  }
};

setFluently(origObj, "a.b", "3");
setFluently(origObj, "a.c", "4");

console.log(JSON.stringify(origObj, null, 3));

Solution 24:[24]

function at(obj, path, val = undefined) {
  // If path is an Array, 
  if (Array.isArray(path)) {
    // it returns the mapped array for each result of the path
    return path.map((path) => at(obj, path, val));
  }
  // Uniting several RegExps into one
  const rx = new RegExp(
    [
      /(?:^(?:\.\s*)?([_a-zA-Z][_a-zA-Z0-9]*))/,
      /(?:^\[\s*(\d+)\s*\])/,
      /(?:^\[\s*'([^']*(?:\\'[^']*)*)'\s*\])/,
      /(?:^\[\s*"([^"]*(?:\\"[^"]*)*)"\s*\])/,
      /(?:^\[\s*`([^`]*(?:\\`[^`]*)*)`\s*\])/,
    ]
      .map((r) => r.source)
      .join("|")
  );
  let rm;
  while (rm = rx.exec(path.trim())) {
    // Matched resource
    let [rf, rp] = rm.filter(Boolean);
    // If no one matches found,
    if (!rm[1] && !rm[2]) {
      // it will replace escape-chars
      rp = rp.replace(/\\(.)/g, "$1");
    }
    // If the new value is set,
    if ("undefined" != typeof val && path.length == rf.length) {
      // assign a value to the object property and return it
      return (obj[rp] = val);
    }
    // Going one step deeper
    obj = obj[rp];
    // Removing a step from the path
    path = path.substr(rf.length).trim();
  }
  if (path) {
    throw new SyntaxError();
  }
  return obj;
}

// Test object schema
let o = { a: { b: [ [ { c: { d: { '"e"': { f: { g: "xxx" } } } } } ] ] } };

// Print source object
console.log(JSON.stringify(o));

// Set value
console.log(at(o, '.a["b"][0][0].c[`d`]["\\"e\\""][\'f\']["g"]', "zzz"));

// Get value
console.log(at(o, '.a["b"][0][0].c[`d`]["\\"e\\""][\'f\']["g"]'));

// Print result object
console.log(JSON.stringify(o));

Solution 25:[25]

Here is my code without using eval. It’s 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

Solution 26:[26]

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.

Example:

{a:{b:11}}.getNestedProperty('a.b'); // Returns 11

The Next.js extension broke Mongoose in my project. Also I've read that it might break jQuery. So, never do it in the Next.js way:

 Object.prototype.getNestedProperty = function (propertyName) {
    var result = this;
    var arr = propertyName.split(".");

    while (arr.length && result) {
        result = result[arr.shift()];
    }

    return result;
};

Solution 27:[27]

This is my extended solution proposed by ninjagecko.

For me, simple string notation was not enough, so the 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 the byIndex method
    return path.split('.').reduce(byIndex, obj)
}

Solution 28:[28]

Use this function:

function dotToObject(data) {
  function index(parent, key, value) {
    const [mainKey, ...children] = key.split(".");
    parent[mainKey] = parent[mainKey] || {};

    if (children.length === 1) {
      parent[mainKey][children[0]] = value;
    } else {
      index(parent[mainKey], children.join("."), value);
    }
  }

  const result = Object.entries(data).reduce((acc, [key, value]) => {
    if (key.includes(".")) {
      index(acc, key, value);
    } else {
      acc[key] = value;
    }

    return acc;
  }, {});
  return result;
}

module.exports = { dotToObject };

Ex:

const user = {
  id: 1,
  name: 'My name',
  'address.zipCode': '123',
  'address.name': 'Some name',
  'address.something.id': 1,
}

const mappedUser = dotToObject(user)
console.log(JSON.stringify(mappedUser, null, 2))

Output:

{
  "id": 1,
  "name": "My name",
  "address": {
    "zipCode": "123",
    "name": "Some name",
    "something": {
      "id": 1
    }
  }
}

Solution 29:[29]

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"]