89

Possible Duplicate:
Accessing nested JavaScript objects with string key

Maybe the title is not clear enough, I just didn't know how to specify what I'm looking for and my English is really bad, sorry.

I'm trying to create function that returns object value, but also plays nice with nested objects. For example:

var obj = {
  foo: { bar: 'baz' }
};

I want to access the value of obj.foo.bar by suppling the string "foo.bar" to the function.

function(obj, path) {
  // Path can be "foo.bar", or just "foo".
}

Thanks!

Machavity
  • 30,841
  • 27
  • 92
  • 100
7elephant
  • 2,839
  • 5
  • 22
  • 17
  • 3
    This is now supported by lodash using _.get(obj, property). See https://lodash.com/docs#get – Dmitri Algazin Aug 22 '17 at 10:21
  • 1
    Since this question was marked as a Duplicate (even it's not) I have to answer in comment. You can use ECMAScript6 Destructuring: `var obj = {foo: { bar: 'baz' }};({foo:{bar:value}} = obj);console.log(value);` – Alexander Feb 21 '18 at 14:45
  • If you want to properly handle any issue while retrieving the value, plus intelligent handling of functions, check out [path-value](https://github.com/vitaly-t/path-value) to help with that. – vitaly-t Dec 25 '20 at 10:36

5 Answers5

107

This works correctly:

var deep_value = function(obj, path){
    for (var i=0, path=path.split('.'), len=path.length; i<len; i++){
        obj = obj[path[i]];
    };
    return obj;
};

Here is the proof / demo: jsfiddle.net/tadeck/5Pt2q/13/

EDIT: I have removed redundant variables, shortened the code.

Tadeck
  • 132,510
  • 28
  • 152
  • 198
  • 3
    beautiful. better than anything in the duplicate http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key – Steve Black Mar 20 '13 at 02:10
  • extended for array support. http://jsfiddle.net/5Pt2q/20/ – Steve Black Jul 30 '13 at 04:02
  • 4
    @SteveBlack better how? It supports a more restricted syntax, and has no error checking for attempting to resolve a key that doesn't exist. – Alnitak Aug 06 '13 at 08:16
  • 1
    I think it would be safer to check for `obj[path[i]] === undefined` – Pier-Luc Gendreau Dec 15 '14 at 23:45
  • any idea how to do this the other way around? I have a nested object/property/key and want the path inside an object as a string. – ProblemsOfSumit Nov 30 '15 at 11:10
  • @Sumit: This is trickier - you would need to know about recurrence and you would need a working way of comparing (eg. object `a` with object `b`, determining if they are equal). If this is not enough, I suggest writing another question, linking this answer, and listing what you have tried (the "recurrence" part and "comparing" parts should be two different functions). – Tadeck Nov 30 '15 at 15:48
  • @Tadeck I just looped through the whole object recursively and added the path to each property again. Talking about a JSON schema. Thanks for your input! – ProblemsOfSumit Nov 30 '15 at 20:39
  • This is bad because it doesn't take into consideration `path.test[0].test` – callback Dec 16 '16 at 09:00
  • 4
    It's bad practice to mutate arguments. – Dmitrii Dushkin Aug 09 '17 at 11:00
  • @Dimitry: Did you check if it works as you described? I did and it is not true that the argument is in any way mutated: http://jsfiddle.net/5Pt2q/242/ – Tadeck Aug 21 '17 at 09:52
  • 1
    @Tadeck yes, it works without problems because arguments is a string, which passed into function as value. If it was object (or array), it will be passed as reference, so changing object argument triggers original object change too. So it's good practice to always not mutate arguments to eliminate possible problems. – Dmitrii Dushkin Aug 22 '17 at 09:20
  • @callback: Maybe 'path.test.0.test' can solve your problem. – Alfonso Nishikawa Sep 25 '17 at 12:16
  • 12
    same for ES6: `const deep_value = (o, p) => p.split('.').reduce((a, v) => a[v], o);` – Reloecc May 16 '18 at 20:09
  • 1
    This fails for cases where the target path is null. See https://codesandbox.io/s/sleepy-lake-srzrcs for a version that handles that case (including some tests). – mpelzsherman Jan 09 '23 at 23:44
66

Consider this:

var obj = {
  foo: { bar: 'baz' }
};

function deepFind(obj, path) {
  var paths = path.split('.')
    , current = obj
    , i;

  for (i = 0; i < paths.length; ++i) {
    if (current[paths[i]] == undefined) {
      return undefined;
    } else {
      current = current[paths[i]];
    }
  }
  return current;
}

console.log(deepFind(obj, 'foo.bar'))
qiao
  • 17,941
  • 6
  • 57
  • 46
18

You mean something like this ? It is a recursive version

function recLookup(obj, path) {
    parts = path.split(".");
    if (parts.length==1){
        return obj[parts[0]];
    }
    return recLookup(obj[parts[0]], parts.slice(1).join("."));
}

See http://jsfiddle.net/kExSr/

fyr
  • 20,227
  • 7
  • 37
  • 53
  • GOLD! I was able to use this to map a path /Root[1]/This/worked to an object {"Root[1]":This{worked:"value"}} . had to trim the leading / and change . to / but otherwise beautiful. – Steve Black Mar 20 '13 at 02:05
  • 3
    this doesn't look too fast. Shouldn't join() the paths again, just check if path is a string or an array – Jonathan Feb 13 '16 at 16:58
10

something like:

function(obj, path) {
  var current=obj; 
  path.split('.').forEach(function(p){ current = current[p]; }); 
  return current;
}
Dan D.
  • 73,243
  • 15
  • 104
  • 123
  • 1
    http://meta.stackexchange.com/a/118023/134069 – T.J. Crowder Jan 11 '12 at 10:11
  • 11
    Another way without mutation: `const lens = (obj, path) => path.split(".").reduce((o, key) => o && o[key] ? o[key] : null, obj);` – Yuriy Gorbylov May 08 '18 at 14:46
  • 1
    @YuriyGorbylov almost perfect, added a typeof to allow falsy values (`0` etc.): `(obj, path) => path.split(".").reduce((o, key) => o && typeof o[key] !== 'undefined' ? o[key] : undefined, obj)` – RienNeVaPlu͢s Sep 20 '22 at 01:46
7

You'd want to split the string on the dot and then repeatedly index into the object, e.g. along the lines of:

function goDeep(obj, path) {
    var parts = path.split('.'),
        rv,
        index;
    for (rv = obj, index = 0; rv && index < parts.length; ++index) {
        rv = rv[parts[index]];
    }
    return rv;
}

Live example

That works because you can access the property of an object in a couple of different ways: There's dotted syntax using a literal (obj.foo), and there's bracketed syntax using a string (obj["foo"]). In the latter case, the string can be the result of any expression, it doesn't have to be a string literal. In in all of the, rv is set to the same value:

rv = obj.foo.bar;
// Or
rv = obj.foo["bar"];
// Or
f = "foo";
rv = obj[f].bar;
// Or
s = "b";
rv = obj.foo[s + "ar"];
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    Surely, the requirement was not expressed, but might we not suspect that OP might equally well want a function call such as `goDeep(myObj, 'bar[3].baz')`? That may be out of scope for the current question... – David Hedlund Jan 11 '12 at 10:09
  • 1
    @DavidHedlund: Fair point, it may well be useful to check for the bracketed form within each `part` in order to be fully compatible with JavaScript's own syntax. I'll leave it as an exercise for the OP. :-) – T.J. Crowder Jan 11 '12 at 10:11
  • (Well, not *fully*, as to do that you'd have to basically re-invent [or -shudder- use] `eval`. But say, mostly compatible with.) – T.J. Crowder Jan 11 '12 at 10:38
  • Well granted, *mostly compatible* is probably preferable over *fully compatible* in this case :) – David Hedlund Jan 11 '12 at 10:43