Categories
javascript nested path

Accessing nested JavaScript objects and arrays by string path

609

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?

7

  • Not sure what you are asking here? You want to be able to query part1.name and have the text “part1.name” returned? Or you want a means to get the value stored within part1.name?

    – BonyT

    Jun 27, 2011 at 10:27

  • have you tried doing like var part1name = someObject.part1name; `

    – Rafay

    Jun 27, 2011 at 10:29

  • 1

    @BonyT : I want to query someObject.part1.name and return the value of it (“Part 1”). However, I want the query (I called it “the key”) to be stored in a variable ‘part1name’. Thanks for your reply. @3nigma : I have certainly do. But that is not my intention. Thanks for the reply.

    – Komaruloh

    Jun 27, 2011 at 10:42

  • 1

    in the duplicate answer, i love fyr’s answer stackoverflow.com/questions/8817394/…

    Mar 20, 2013 at 2:09

  • 1

643

I just made this based on some similar code I already had, it appears to work:

Object.byString = function(o, s) {
    s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
    s = s.replace(/^\./, '');           // strip a leading dot
    var a = s.split('.');
    for (var i = 0, n = a.length; i < n; ++i) {
        var k = a[i];
        if (k in o) {
            o = o[k];
        } else {
            return;
        }
    }
    return o;
}

Usage::

Object.byString(someObj, 'part3[0].name');

See a working demo at http://jsfiddle.net/alnitak/hEsys/

EDIT some have noticed that this code will throw an error if passed a string where the left-most indexes don’t correspond to a correctly nested entry within the object. This is a valid concern, but IMHO best addressed with a try / catch block when calling, rather than having this function silently return undefined for an invalid index.

31

  • 34

    This works beautifully. Please contribute this to the internet by wrapping it as a node package.

    – t3dodson

    Jan 13, 2015 at 22:38

  • 17

    @t3dodson I just did: github.com/capaj/object-resolve-path just be aware that this doesn’t play nice when your property name contains ‘[]’ in itself. Regex will replace it with ‘.’ and it doesn’t work as expected

    – Capaj

    Jul 30, 2015 at 21:57


  • 44

    great stuff; using the lodash library, one can also do: _.get(object, nestedPropertyString);

    – ian

    Aug 13, 2015 at 12:49

  • 19

    This will probably get lost in the sea of comments, however it errors if you try and address a property that doesn’t exist. So 'part3[0].name.iDontExist'. Adding a check to see if o is an object in the if in fixes the issue. (How you go about that is up-to you). See updated fiddle: jsfiddle.net/hEsys/418

    – ste2425

    Nov 17, 2015 at 11:12

  • 2

    @ThatGuyRob introducing a third party library is not always “better”, and in any event that method didn’t even exist when I wrote this answer.

    – Alnitak

    Oct 21, 2019 at 16:44

256

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'

6

  • 9

    This should be the only accepted answer, because this is the only one working for both dot and bracket syntax and It doesn’t fail, when we have ‘[]’ in the string of a key in the path.

    – Capaj

    Aug 4, 2015 at 2:12

  • 10

    This. Plus, it supports _.set(...)

    – Josh C.

    Aug 22, 2017 at 6:23

  • what happes if the objet is not found?

    – DDave

    Oct 31, 2017 at 16:58

  • @DDave if the value passed as the object is undefined or not an object, _.get will show the same behavior as when no key is found in the provided object. eg _.get(null, "foo") -> undefined, _.get(null, "foo", "bar") -> "bar". However this behavior is not defined in the docs so subject to change.

    Nov 2, 2017 at 0:40

  • 17

    @Capaj you kiddin’? And who doesn’t want/can’t use lodash?

    Feb 19, 2018 at 21:21

231

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.

12

  • 7

    using reduce is an excellent solution (one can also use _.reduce() from the underscore or lodash library)

    – Alp

    May 22, 2014 at 14:51

  • 5

    I think self is probably undefined here. Do you mean this?

    Jun 17, 2014 at 3:37

  • 2

    Here’s my complement to set values by path: pastebin.com/jDp5sKT9

    – mroach

    Jan 2, 2017 at 8:42

  • 1

    @SC1000 good idea. This answer was written before default parameters were available in most browsers. I’ll update it to “function resolve(path, obj=self)”, since referencing the global object as a default is intentional.

    – speigg

    Aug 15, 2018 at 0:29


  • 2

    @AdamPlocher I know this is old now, but I converted this to typescript as follows: export function resolvePath(path: string | string[], obj: any, separator = '.') { const properties = Array.isArray(path) ? path : path.split(separator); return properties.reduce((prev, curr) => prev && prev[curr], obj); }

    – crush

    Apr 13, 2020 at 7:12