[javascript] Accessing nested JavaScript objects and arrays by string path

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?

This question is related to javascript jquery path nested

The answer is


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

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

Inspired by @webjay's answer: https://stackoverflow.com/a/46008856/4110122

I made this function which can you use it to Get/ Set/ Unset any value in object

function Object_Manager(obj, Path, value, Action) 
{
    try
    {
        if(Array.isArray(Path) == false)
        {
            Path = [Path];
        }

        let level = 0;
        var Return_Value;
        Path.reduce((a, b)=>{
            level++;
            if (level === Path.length)
            {
                if(Action === 'Set')
                {
                    a[b] = value;
                    return value;
                }
                else if(Action === 'Get')
                {
                    Return_Value = a[b];
                }
                else if(Action === 'Unset')
                {
                    delete a[b];
                }
            } 
            else 
            {
                return a[b];
            }
        }, obj);
        return Return_Value;
    }

    catch(err)
    {
        console.error(err);
        return obj;
    }
}

To use it:

 // Set
 Object_Manager(Obj,[Level1,Level2,Level3],New_Value, 'Set');

 // Get
 Object_Manager(Obj,[Level1,Level2,Level3],'', 'Get');

 // Unset
 Object_Manager(Obj,[Level1,Level2,Level3],'', 'Unset');

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.


The solutions here are just for accessing the deeply nested keys. I needed one for accessing, adding, modifying and deleting the keys. This is what I came up with:

var deepAccessObject = function(object, path_to_key, type_of_function, value){
    switch(type_of_function){
        //Add key/modify key
        case 0: 
            if(path_to_key.length === 1){
                if(value)
                    object[path_to_key[0]] = value;
                return object[path_to_key[0]];
            }else{
                if(object[path_to_key[0]])
                    return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value);
                else
                    object[path_to_key[0]] = {};
            }
            break;
        //delete key
        case 1:
            if(path_to_key.length === 1){
                delete object[path_to_key[0]];
                return true;
            }else{
                if(object[path_to_key[0]])
                    return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value);
                else
                    return false;
            }
            break;
        default:
            console.log("Wrong type of function");
    }
};
  • path_to_key: path in an array. You can replace it by your string_path.split(".").
  • type_of_function: 0 for accessing(dont pass any value to value), 0 for add and modify. 1 for delete.

React example - Using lodash

This may not be the most efficient way, from a performance perspective, but if your app is some monolith it sure as heck will save you some time. Especially, when you are tightly coupling your state data format to an API back-end.

   import set from "lodash/set";  // More efficient import

    class UserProfile extends Component {

      constructor(props){
        super(props);

        this.state = {
          user: {
            account: {
              id: "",
              email: "",
              first_name: ""
            }
          }
        }
      }

       /**
       * Updates the state based on the form input
       * 
       * @param {FormUpdate} event 
       */
      userAccountFormHook(event) {
        // https://lodash.com/docs#get
        // https://lodash.com/docs#set
        const { name, value } = event.target;
        let current_state = this.state
        set(current_state, name, value)  // Magic happens here
        this.setState(current_state);
      }

    render() {
        return (
          <CustomFormInput
            label: "First Name"
            type: "text"
            placeholder: "First Name"
            name: "user.account.first_name"
            onChange: {this.userAccountFormHook}
            value: {this.state.user.account.first_name}

          />
      )
  }
}

Building off of Alnitak's answer:

if(!Object.prototype.byString){
  //NEW byString which can update values
Object.prototype.byString = function(s, v, o) {
  var _o = o || this;
      s = s.replace(/\[(\w+)\]/g, '.$1'); // CONVERT INDEXES TO PROPERTIES
      s = s.replace(/^\./, ''); // STRIP A LEADING DOT
      var a = s.split('.'); //ARRAY OF STRINGS SPLIT BY '.'
      for (var i = 0; i < a.length; ++i) {//LOOP OVER ARRAY OF STRINGS
          var k = a[i];
          if (k in _o) {//LOOP THROUGH OBJECT KEYS
              if(_o.hasOwnProperty(k)){//USE ONLY KEYS WE CREATED
                if(v !== undefined){//IF WE HAVE A NEW VALUE PARAM
                  if(i === a.length -1){//IF IT'S THE LAST IN THE ARRAY
                    _o[k] = v;
                  }
                }
                _o = _o[k];//NO NEW VALUE SO JUST RETURN THE CURRENT VALUE
              }
          } else {
              return;
          }
      }
      return _o;
  };

}

This allows you to set a value as well!

I've created an npm package and github with this as well


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

/**
 * 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")

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


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

This will probably never see the light of day... but here it is anyway.

  1. Replace [] bracket syntax with .
  2. Split on . character
  3. Remove blank strings
  4. Find the path (otherwise undefined)

(For finding a path to an object, use this pathTo solution.)

_x000D_
_x000D_
// "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
_x000D_
_x000D_
_x000D_


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


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


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'

Starting from @Alnitak answer I built this source, which downloads an actual .JSON file and processes it, printing to console explanatory strings for each step, and more details in case of wrong key passed:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
  <script>
function retrieveURL(url) {
        var client = new XMLHttpRequest();
        prefix = "https://cors-anywhere.herokuapp.com/"
        client.open('GET', prefix + url);
        client.responseType = 'text';
        client.onload = function() {
            response = client.response; // Load remote response.
            console.log("Response received.");
            parsedJSON  = JSON.parse(response);
            console.log(parsedJSON);
            console.log(JSONitemByPath(parsedJSON,"geometry[6].obs[3].latituade"));
            return response;
        };
        try {
            client.send();
        } catch(e) {
            console.log("NETWORK ERROR!");
            console.log(e);
        }
}



function JSONitemByPath(o, s) {
    structure = "";
    originalString = s;
    console.log("Received string: ", s);
    s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
    console.log("Converted to   : ", s);
    s = s.replace(/^\./, '');           // strip a leading dot
    var a = s.split('.');

    console.log("Single keys to parse: ",a);

    for (var i = 0, n = a.length; i < n; ++i) {
        var k = a[i];
        if (k in o) {
            o = o[k];
            console.log("object." + structure +  a[i], o);
            structure +=  a[i] + ".";
        } else {
            console.log("ERROR: wrong path passed: ", originalString);
            console.log("       Last working level: ", structure.substr(0,structure.length-1));
            console.log("       Contents: ", o);
            console.log("       Available/passed key: ");
            Object.keys(o).forEach((prop)=> console.log("       "+prop +"/" + k));
            return;
        }
    }
    return o;
}


function main() {
    rawJSON = retrieveURL("http://haya2now.jp/data/data.json");
}

</script>
  </head>
  <body onload="main()">
  </body>
</html>

Output example:

Response received.
json-querier.html:17 {geometry: Array(7), error: Array(0), status: {…}}
json-querier.html:34 Received string:  geometry[6].obs[3].latituade
json-querier.html:36 Converted to   :  geometry.6.obs.3.latituade
json-querier.html:40 Single keys to parse:  (5) ["geometry", "6", "obs", "3", "latituade"]
json-querier.html:46 object.geometry (7) [{…}, {…}, {…}, {…}, {…}, {…}, {…}]
json-querier.html:46 object.geometry.6 {hayabusa2: {…}, earth: {…}, obs: Array(6), TT: 2458816.04973593, ryugu: {…}, …}
json-querier.html:46 object.geometry.6.obs (6) [{…}, {…}, {…}, {…}, {…}, {…}]
json-querier.html:46 object.geometry.6.obs.3 {longitude: 148.98, hayabusa2: {…}, sun: {…}, name: "DSS-43", latitude: -35.4, …}
json-querier.html:49 ERROR: wrong path passed:  geometry[6].obs[3].latituade
json-querier.html:50        Last working level:  geometry.6.obs.3
json-querier.html:51        Contents:  {longitude: 148.98, hayabusa2: {…}, sun: {…}, name: "DSS-43", latitude: -35.4, …}
json-querier.html:52        Available/passed key: 
json-querier.html:53        longitude/latituade
json-querier.html:53        hayabusa2/latituade
json-querier.html:53        sun/latituade
json-querier.html:53        name/latituade
json-querier.html:53        latitude/latituade
json-querier.html:53        altitude/latituade
json-querier.html:18 undefined

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


Works for arrays / arrays inside the object also. Defensive against invalid values.

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


AngularJS has $scope.$eval

With AngularJS, one can use the $scope.$eval method to access nested objects:

$scope.someObject = someObject;
console.log( $scope.$eval("someObject.part3[0].name") ); //Part 3A

For more information, see

The DEMO

_x000D_
_x000D_
angular.module("app",[])
.run(function($rootScope) {
     $rootScope.someObject = {
         'part2' : {
              'name': 'Part 2',
              'size': '15',
              'qty' : '60'
         },
         'part3' : [{
              'name': 'Part 3A',
              'size': '10',
              'qty' : '20'
         },{
              name: 'Part 3B'           
         }]
     };
     console.log(
         "part3[0].name =",
         $rootScope.$eval("someObject.part3[0].name")
    );
})
_x000D_
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app"
</body>
_x000D_
_x000D_
_x000D_


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 value of a deep object member with dot notation without any external JavaScript library with the simple following trick:

new Function('_', 'return _.' + path)(obj);

In your case to obtain value of part1.name from someObject just do:

new Function('_', 'return _.part1.name')(someObject);

Here is a simple fiddle demo: https://jsfiddle.net/harishanchu/oq5esowf/


Instead of a string an array can be used adressing nested objects and arrays e.g.: ["my_field", "another_field", 0, "last_field", 10]

Here is an example that would change a field based on this array representation. I am using something like that in react.js for controlled input fields that change the state of nested structures.

let state = {
        test: "test_value",
        nested: {
            level1: "level1 value"
        },
        arr: [1, 2, 3],
        nested_arr: {
            arr: ["buh", "bah", "foo"]
        }
    }

function handleChange(value, fields) {
    let update_field = state;
    for(var i = 0; i < fields.length - 1; i++){
        update_field = update_field[fields[i]];
    }
    update_field[fields[fields.length-1]] = value;
}

handleChange("update", ["test"]);
handleChange("update_nested", ["nested","level1"]);
handleChange(100, ["arr",0]);
handleChange('changed_foo', ["nested_arr", "arr", 3]);
console.log(state);

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


What about this solution:

setJsonValue: function (json, field, val) {
  if (field !== undefined){
    try {
      eval("json." + field + " = val");
    }
    catch(e){
      ;
    }
  }  
}

And this one, for getting:

getJsonValue: function (json, field){
  var value = undefined;
  if (field !== undefined) {
    try {
      eval("value = json." + field);
    } 
    catch(e){
      ;
    }
  }
  return value;
};

Probably some will consider them unsafe, but they must be much faster then, parsing the string.


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: '[email protected]',
    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.


Using object-scan this becomes a one liner. However more importantly this solution considers performance:

  • input traversed once during search (even if multiple keys are queried)
  • parsing only happens once on init (in case multiple objects are queried)
  • allow for extended syntax using *

_x000D_
_x000D_
// const objectScan = require('object-scan');

const 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' }] };

const get = (haystack, needle) => objectScan([needle], { rtn: 'value', abort: true })(haystack);

console.log(get(someObject, 'part1.name'));
// => Part 1
console.log(get(someObject, 'part2.qty'));
// => 60
console.log(get(someObject, 'part3[0].name'));
// => Part 3A

const getAll = (haystack, ...needles) => objectScan(needles, { reverse: false, rtn: 'entry', joined: true })(haystack);

console.log(getAll(someObject, 'part1.name', 'part2.qty', 'part3[0].name'));
/* =>
[ [ 'part1.name', 'Part 1' ],
  [ 'part2.qty', '60' ],
  [ 'part3[0].name', 'Part 3A' ] ]
 */

console.log(getAll(someObject, 'part1.*'));
/* =>
[ [ 'part1.name', 'Part 1' ],
  [ 'part1.size', '20' ],
  [ 'part1.qty', '50' ] ]
 */
_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


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

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

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

Working with Underscore's property or propertyOf:

_x000D_
_x000D_
var test = {_x000D_
  foo: {_x000D_
    bar: {_x000D_
      baz: 'hello'_x000D_
    }_x000D_
  }_x000D_
}_x000D_
var string = 'foo.bar.baz';_x000D_
_x000D_
_x000D_
// document.write(_.propertyOf(test)(string.split('.')))_x000D_
_x000D_
document.write(_.property(string.split('.'))(test));
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
_x000D_
_x000D_
_x000D_

Good Luck...


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.

_x000D_
_x000D_
if (Object.byPath === undefined) {_x000D_
  Object.byPath = (obj, path) => path_x000D_
    .replace(/\[(\w+)\]/g, '.$1')_x000D_
    .replace(/^\./, '')_x000D_
    .split(/\./g)_x000D_
    .reduce((ref, key) => key in ref ? ref[key] : ref, obj)_x000D_
}_x000D_
_x000D_
const data = {_x000D_
  foo: {_x000D_
    bar: [{_x000D_
      baz: 1_x000D_
    }]_x000D_
  }_x000D_
}_x000D_
_x000D_
console.log(Object.byPath(data, 'foo.bar[0].baz'))
_x000D_
_x000D_
_x000D_


Extension of Mohamad Hamouday' Answer will fill in missing keys

function Object_Manager(obj, Path, value, Action, strict) 
{
    try
    {
        if(Array.isArray(Path) == false)
        {
            Path = [Path];
        }

        let level = 0;
        var Return_Value;
        Path.reduce((a, b)=>{
            console.log(level,':',a, '|||',b)
            if (!strict){
              if (!(b in a)) a[b] = {}
            }


            level++;
            if (level === Path.length)
            {
                if(Action === 'Set')
                {
                    a[b] = value;
                    return value;
                }
                else if(Action === 'Get')
                {
                    Return_Value = a[b];
                }
                else if(Action === 'Unset')
                {
                    delete a[b];
                }
            } 
            else 
            {
                return a[b];
            }
        }, obj);
        return Return_Value;
    }

    catch(err)
    {
        console.error(err);
        return obj;
    }
}

Example


obja = {
  "a": {
    "b":"nom"
  }
}

// Set
path = "c.b" // Path does not exist
Object_Manager(obja,path.split('.'), 'test_new_val', 'Set', false);

// Expected Output: Object { a: Object { b: "nom" }, c: Object { b: "test_new_value" } }


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, there is support for full ES5 JavaScript syntax too. It just needs to be tokenized:

const {resolveValue, tokenizePath} = require('path-value');

const path = tokenizePath('part3[0].name');
//=> ['part3', '0', 'name']

resolveValue(someObject, path); //=> Part 3A

Note that the following will not work for all valid unicode property names (but neither will any of the other answers as far as I can tell).

_x000D_
_x000D_
const PATTERN = /[\^|\[|\.]([$|\w]+)/gu

function propValue(o, s) {
    const names = []
    for(let [, name] of [...s.matchAll(PATTERN)]) 
        names.push(name)
    return names.reduce((p, propName) => {
        if(!p.hasOwnProperty(propName)) 
            throw 'invalid property name'
        return p[propName]
    }, o)
}

let path = 'myObject.1._property2[0][0].$property3'
let o = {
    1: {
        _property2: [
            [{
                $property3: 'Hello World'
            }]
        ]
    }
}
console.log(propValue(o, path)) // 'Hello World'
_x000D_
_x000D_
_x000D_


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

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


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

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to jquery

How to make a variable accessible outside a function? Jquery assiging class to th in a table Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Getting all files in directory with ajax Bootstrap 4 multiselect dropdown Cross-Origin Read Blocking (CORB) bootstrap 4 file input doesn't show the file name Jquery AJAX: No 'Access-Control-Allow-Origin' header is present on the requested resource how to remove json object key and value.?

Examples related to path

Get Path from another app (WhatsApp) How to serve up images in Angular2? How to create multiple output paths in Webpack config Setting the correct PATH for Eclipse How to change the Jupyter start-up folder Setting up enviromental variables in Windows 10 to use java and javac How do I edit $PATH (.bash_profile) on OSX? Can't find SDK folder inside Android studio path, and SDK manager not opening Get the directory from a file path in java (android) Graphviz's executables are not found (Python 3.4)

Examples related to nested

Nested routes with react router v4 / v5 Extract first item of each sublist python "TypeError: 'numpy.float64' object cannot be interpreted as an integer" How can I combine multiple nested Substitute functions in Excel? Retrieving values from nested JSON Object MySQL Nested Select Query? List comprehension on a nested list? Nested ifelse statement Single Line Nested For Loops Nested or Inner Class in PHP