1

If I have a base object which is always the same: project but sometimes I have to access its fields dynamically, how can I access its fields when it can be 1 or more nested objects for example:

function (myPath){
  return project[myPath];

}

This works when using project["oneField"] in myPath("oneField")

but it does not work when it is nested to or more levels:

myPath("one.two.fieldName") does not work: project["one.two.fieldName"]

Neither like this: project."one.two.fieldName"

CommonSenseCode
  • 23,522
  • 33
  • 131
  • 186
  • When you ask for `project["one.two.fieldName"]` you are checking if `project` has a key literally called `"one.two.fieldName"` including the dots. You can just split on dot and get an array that contains key and then traverse the object that way `obj= obj[keys[i]]`. Or other similar techniques that boil down to "split - use the results for navigating". – VLAZ Oct 26 '16 at 19:35
  • project["one"]["two"]["fieldName"] – kevin ternet Oct 26 '16 at 19:36

3 Answers3

15

You could do it like this (ES6):

function getVal(object, path){
    return path.split('.').reduce ( (res, prop) => res[prop], object );
}

// sample data
var object = { one: { two: { fieldName: 'gotcha' } } };

// get inner field value
console.log( getVal(object, 'one.two.fieldName') );

You can further extend this function to also support square brackets:

function getVal(object, path){
    const regex = /(?<=\[)(?!")[^\]]+|(?<=\[")[^"]+|[^."[\]]+/g;
    return path.match(regex).reduce ( (res, prop) => res[prop], object );
}

let object = { 'a': [{ 'b': { 'c': 3 } }] };
console.log( getVal(object, 'a[0].b["c"]') );
trincot
  • 317,000
  • 35
  • 244
  • 286
  • the best solution so far! – me1111 Nov 12 '18 at 14:00
  • I really like this solution too, but this can produce an error if the nested objected doesn't exists ("cannot read prop of null"), this can be fixed with: return myPath.split('.').reduce ( (res, prop) => res && res[prop], project ); – JollyBrackets Dec 18 '18 at 21:44
  • @JollyBrackets, an error seems the right thing when referencing a property of something that is not an object. It is the same behaviour as you would do `{a: 1}.a.b.c`. But if you prefer to return `undefined` I would put the `return` in a `try` `catch` block. That has the advantage that `reduce` is really interrupted and does not perform unnecessary extra iterations, and that the `reduce` callback does not have to execute the extra test in every iteration. Note that your workaround would return 0 for `getVal({a: 0}, 'a.b.c.d')` – trincot Dec 18 '18 at 23:36
  • if you have an array inside of your object this solution does no work lets say your object is object = { 'a': [{ 'b': { 'c': 3 } }] }; and path is 'a[0].b.c' then this will give out an error – md shoaib Feb 06 '22 at 07:43
  • @mdshoaib, but that is a syntax that is not specified in the question. You'd have to pass the path as `'a.0.b.c'`. I just added a version to my answer that covers square brackets. Hope it helps you. – trincot Feb 06 '22 at 08:56
2

option #1 (not recommended) - use the eval function:

var projects = {
  a : {
    b : {
      c : 1
    }
  }
}
function get(myPath) {
  debugger;
  return eval("projects." + myPath)
}
console.log(get('a.b.c'))

option #2 - split by . and go over the elements in the object:

var projects = {
  a: {
    b : { 
      c : '1'
    }
  }
}

function get(path) {
  if (path.indexOf('.')) {
    subs = path.split(".")
    ret = projects;
    for (var i = 0; i < subs.length; i++) {
      ret = ret[subs[i]]
    }
    return ret;
  } else {
    return projects[path];
  }
}

console.log(get('a'))
console.log(get('a.b'))
console.log(get('a.b.c'))
Community
  • 1
  • 1
Dekel
  • 60,707
  • 10
  • 101
  • 129
1

Usually if I'm doing something like this, I'll use a recursive function:

var data = {a: {b: {c: 5}}};

function getValue(obj, key) {
    var parts = key.split('.');
    return obj 
        && (parts.length === 1 
            && obj[key] || getValue(obj[parts[0]], parts.slice(1).join('.'))) 
    || null;
}

console.log(getValue(data, "a.b.c"));

JSFiddle

It's a bit concisely written, but basically if it has a key with a dot in it, it'll call it down a level. If it ever gets an obj that doesn't exist, it'll return null. Otherwise, once it's down to the last level, it'll return the value it found.

A more expanded, maybe easier to understand, version is:

function getValue(obj, key) {
    var parts = key.split('.');
    if (!obj) {
        return null;
    } else if (parts.length === 1) {
        return obj[key];
    }

    return getValue(obj[parts.slice(1).join('.')], key);
}
samanime
  • 25,408
  • 15
  • 90
  • 139