Ariel
Ariel

Reputation: 26783

In javascript how can I dynamically get a nested property of an object

var arr = { foo : 1, bar: { baz : 2 }, bee : 3 }

function getter(variable) {
  return arr[variable];
}

If I want 'foo' vs 'bee' I can just do arr[variable] - that's easy, and the function does that.

But what if I want to get arr.bar.baz AKA arr[bar][baz]?

What can I pass to the getter function that will let me do that, (and of course also let me get non-nested properties using the same function).

I tried getter('bar.baz') and getter('[bar][baz]') but those didn't work.

I suppose I can parse for dots or brackets (like here: In javascript, test for property deeply nested in object graph?). Is there a cleaner way? (Besides eval of course.)

Especially because I need to get the deeply set properly many many times in a loop for a bunch of array elements.

Upvotes: 61

Views: 56083

Answers (17)

Avira George
Avira George

Reputation: 1

This is my first answer in stack overflow hope this goes well :)

So i was facing a similar issue. In my case it was like i have and array of objects and an array of keys for which i need to get the values as array.

For example following is the array of objects

let arr = [{ name: "Lanister",
      address: {
        houseNumber: 34,
        street: "Casterly Rock",
        pin: "123456",
      },
      phone: "37232112221"
    },{
      name: "Jon Snow",
      address: {
        houseNumber: 34,
        street: "Winterfell",
        pin: "876543",
      },
      phone: "23423432"
    }
 ];

Following is the array of keys for which i need the values.

["name", "address.street", "address.pin"]

Following function will take both the parameters and return the value of arrays.

const generateTableData = (listData, displayItems = []) => {
    let dataList = listData.map((item) => {
        let resArr = displayItems.map((dis) => {
            let nestedEle = dis.split(".");
            let candidate = item;
            let finalRes = null;
            for(let i = 0; i <= nestedEle.length-1; i++) {
                candidate = candidate[nestedEle[i]];
                if(candidate !== undefined) {
                    finalRes = candidate;
                } else {
                    break;
                }
            }
            return finalRes;
        });

        return resArr;
    });
    return dataList;
}

Consoled result follows

[
  [ 'Lanister', 'Casterly Rock', '123456' ],
  [ 'Jon Snow', 'Winterfell', '876543' ]
]

Upvotes: 0

Ivy O&#39;Neal-Odom
Ivy O&#39;Neal-Odom

Reputation: 111

Others have described elegant solutions for reading deeply nested parameters, but you can also edit them by the miracle of recursion:

let myObj = {
  "foo": {
    "bar": {
      "fee": {
        "fi": {
          "foe": "fum"
        }
      }
    }
  }
}

let myPath = "foo.bar.fee.fi.foe";
let myValue = "I smell the blood of an englishman";
setObjectPropertyByDynamicPath(myObj, myPath, myValue);

console.log(myObj);


function setObjectPropertyByDynamicPath(object, path, value){
  let splitMyPath = path.split(".");
  if(splitMyPath.length >= 1){
    return sOPBDPHelper(object, splitMyPath, value);
  }else{
    console.error("Bad Split Path: " + splitMyPath.toString())
    return object;
  }
}

function sOPBDPHelper(object, pathArray, value){
  if(pathArray.length == 1){
    object[pathArray[0]] = value;
    return object;
  }else{
    object[pathArray[0]] = sOPBDPHelper(object[pathArray[0]], pathArray.slice(1, pathArray.length), value);
    return object;
  }
}

It requires neither eval() nor any libraries.

Upvotes: 1

Doobied
Doobied

Reputation: 9

Super simple implementation to trace value from deeply nested object dynamically.

const exampleObj = {
  collection: {
    info: {
      name: 'my name'
    }
  }
}


function getDeepObjectValue(obj, key) {

    const words = key.split('.');

    for (var word of words) {
        if (obj[word]) {
            obj = obj[word]
        }
    }

    return obj

}

console.log(getDeepObjectValue(exampleObj, "collection.info.name"))
console.log(getDeepObjectValue(exampleObj, "collection.info"))

Upvotes: 0

Abdullah Sohail
Abdullah Sohail

Reputation: 435

Here's a very simple one liner which grants you dynamic access via "foo.bar.baz" mechanism,

var obj = {
  foo: {
    bar: {
      baz: 'foobarbaz'
    }
  }
}
const nestedAccess = "foo.bar.baz";
console.log(nestedAccess.split('.').reduce((prev, cur) => prev[cur], obj)) //'foobarbaz'

Upvotes: 8

Matheus Felipe
Matheus Felipe

Reputation: 123

Here I created a small suite of functions to 'get / 'set' / 'push' / 'pull' from object nested properties.

inputObject : Target object. Ex: obj = {a:1, b:{c:2,d:3}}

propertyString : String containing the key to access. Ex: "b.c"

Finally:

_getObjectValueByPathString(obj, "b.c") would return 2

function _getObjectValueByPathString(inputObject, propertyString) {
    let splitStr = propertyString.split('.');

    if (!inputObject.hasOwnProperty(splitStr[0])) return undefined;

    if (splitStr.length === 1) {
        return inputObject[splitStr[0]];
    }
    else if (splitStr.length > 1) {
        let newPropertyString = "";
        let firstValue = splitStr.shift();

        splitStr.forEach((subStr, i) => {
            newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
        });

        return _getObjectValueByPathString(inputObject[firstValue], newPropertyString);
    }
    else {
        throw "Invalid property string provided";
    }
}

function _setObjectValueByPathString(inputObject, propertyString, inputValue) {
    let splitStr = propertyString.split('.');

    if (splitStr.length === 1) {
        inputObject[splitStr[0]] = inputValue;
        return;
    }
    else if (splitStr.length > 1) {
        let newPropertyString = "";
        let firstValue = splitStr.shift();

        splitStr.forEach((subStr, i) => {
            newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
        });

        _setObjectValueByPathString(inputObject[firstValue], newPropertyString, inputValue);
        return;
    }
    else {
        throw "Invalid property string provided";
    }
}

function _pushObjectValueByPathString(inputObject, propertyString, inputValue) {
    let splitStr = propertyString.split('.');

    if (splitStr.length === 1) {
        inputObject[splitStr[0]].push(inputValue);
        return;
    }
    else if (splitStr.length > 1) {
        let newPropertyString = "";
        let firstValue = splitStr.shift();

        splitStr.forEach((subStr, i) => {
            newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
        });

        _pushObjectValueByPathString(inputObject[firstValue], newPropertyString, inputValue);
        return;
    }
    else {
        throw "Invalid property string provided";
    }
}

function _pullObjectValueByPathString(inputObject, propertyString, inputValue) {
    let splitStr = propertyString.split('.');

    if (splitStr.length === 1) {
        inputObject[splitStr[0]].pull(inputValue);
        return;
    }
    else if (splitStr.length > 1) {
        let newPropertyString = "";
        let firstValue = splitStr.shift();

        splitStr.forEach((subStr, i) => {
            newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
        });

        _pullObjectValueByPathString(inputObject[firstValue], newPropertyString, inputValue);
        return;
    }
    else {
        throw "Invalid property string provided";
    }
}

Upvotes: 0

Satish
Satish

Reputation: 151

Using reduce we can fetch the value in single line of code.

const testobj = {b:{c:'1', d:{e:'2',f:'3'}}, g:{h:'3'}}

function fetchByDotOperator(object, value) {
    return value.split('.').reduce((acc, curr) => acc[curr], object);
}
console.log(fetchByDotOperator(testobj,'b.d.e'))

Upvotes: 10

Abhijeet Bagul
Abhijeet Bagul

Reputation: 19

let obj = {foo : {bar: {baz:1}}}; // -- simply

console.log(eval('obj.foo.bar.baz')); //-- 1

// -- safer

val = "";
try {
    val = eval('Obj.foo.bar.baz')
}
catch(e) {
    val = "empty"
}

// -- val = 1

// -- use at your risk ;)

Upvotes: 0

RobG
RobG

Reputation: 147513

You can use a deep access function based on a string for the path. Note that you can't have any periods in the property names.

function getPropByString(obj, propString) {
  if (!propString)
    return obj;

  var prop, props = propString.split('.');

  for (var i = 0, iLen = props.length - 1; i < iLen; i++) {
    prop = props[i];

    var candidate = obj[prop];
    if (candidate !== undefined) {
      obj = candidate;
    } else {
      break;
    }
  }
  return obj[props[i]];
}

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

console.log(getPropByString(obj, 'foo.bar.baz')); // x
console.log(getPropByString(obj, 'foo.bar.baz.buk')); // undefined

If the access string is empty, returns the object. Otherwise, keeps going along access path until second last accessor. If that's an ojbect, returns the last object[accessor] value. Otherwise, returns undefined.

Upvotes: 61

K Kreid
K Kreid

Reputation: 103

Above answers help you access nested objects only, however you might also want to access data in an object/array data type. You can try this recusive method:

const getValue = (obj, key) => {
  const keyParts = key.split(".");
  return getValueHelper(obj, keyParts);
};

const getValueHelper = (obj, keyParts) => {
  if (keyParts.length == 0) return obj;
  let key = keyParts.shift();
  if (Array.isArray(obj[key])) {
    return obj[key].map((x) => getValueHelper(x, [...keyParts])).flat();
  }
  return getValueHelper(obj[key], [...keyParts]);
};


//Examples

let data1 = {
  a: [{ b: { c: [{ d: [{ e: 1 }] }] } }, { b: { c: [{ d: [{ e: 2 }] }] } }],
};

console.log(getValue(data1, "a.b.c.d.e"));

//Output
//[ 1, 2 ]

let data2 = {
  a:{b:1},
};

console.log(getValue(data2, "a.b"));
//Output
//1

p.s. Remove .flat() to get desired output for arrays.

Upvotes: 2

 function getPropertyByString(object, propString) {
  let value = object;

  const props = propString.split('.');
  for (let index = 0; index < props.length; index += 1) {
    if (props[index] === undefined) break;
    value = value[props[index]];
  }
  return value;
};

const object = {
  name: 'any_name',
  address: {
    number: 77,
    test: {
      name: 'test'
    }
  }
}

console.log(getPropertyByString(object, 'address.test.name'))
// test

Upvotes: 1

Ninja
Ninja

Reputation: 2098

Using ES6:

var arr = { foo : 1, bar: { baz : 2 }, bee : 3 };
var {foo, bar, bar: {baz}, bee} = arr;

Same as:

// var foo = 1;
// var bar = {baz: 2};
// var baz = 2;
// var bee = 3;

Using lodash: https://lodash.com/docs#get

_.get(arr, 'bar.baz'); //returns 2;
_.get(arr, 'bar.baz[5].bazzz'); //returns undefined wont throw error;
_.get(arr, 'bar.baz[5].bazzz', 'defaultvalue'); // Returns defaultValue because result is undefined 

Upvotes: 38

Vincent Viton
Vincent Viton

Reputation: 181

A recursive way :

   function getValue(obj, path) {
        if (!path) return obj;
        const properties = path.split('.');
        return getValue(obj[properties.shift()], properties.join('.'))
    }

    const myObj = {
        foo: {
            bar: {
                value: 'good'
            }
        }
    }

    console.log(getValue(myObj, 'foo.bar.value')); // good

Upvotes: 18

theonelucas
theonelucas

Reputation: 614

A one liner for you:

const mock = {
  target: {
    "prop1": {
      "prop2": {
        "prop3": "sad"
      }
    }
  },
  path: "prop1.prop2.prop3",
  newValue: "happy"
};

mock.path.split(".").reduce(
  (acc, curr, i, src) =>
    (curr === src[src.length - 1]) ? acc[src[src.length - 1]] = mock.newValue : acc[curr], mock.target);


console.log(mock.target); //? { prop1: { prop2: { prop3: 'happy' } } }

Upvotes: 5

Redu
Redu

Reputation: 26201

I have recently developed my own Object method to get an object property nested among objects and arrays regardless how deep it is. It utilizes a single line of recursive approach. Check this out.

Object.prototype.getNestedValue = function(...a) {
  return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};

var myObj = { foo : 1, bar: { baz : 2 }, bee : 3 },
   bazval = myObj.getNestedValue("bar","baz");

document.write(bazval);

Now let's check a deeper nested array object combo data structure

Object.prototype.getNestedValue = function(...a) {
  return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};

var myArr = [{fox: [{turn:[857, 432]}]}, {sax: [{pana:[777, 987]}]}, {ton: [{joni:[123, 567]}]}, {piu: [{burn:[666, 37]}]}, {sia: [{foxy:[404, 696]}]}];
  
document.write(myArr.getNestedValue(3,"piu",0,"burn",1));

I believe being able to pass search parameters dynamically to existing array methods would make actions like searching, filtering or replacing of deeply nested structures much easy.

Upvotes: 2

Ben
Ben

Reputation: 1213

Theres a function defined on this blog to safely read nested properties from a JS object

It allows you to mine an object for properties... ie.

safeRead(arr, 'foo', 'bar', 'baz');

and if any part of the object chain is null or undefined it returns an empty string....

Upvotes: 0

Daff
Daff

Reputation: 44215

You can access the functions arguments where you can pass any number of strings. I also recommend using arr as a parameter for better encapsulation:

function getter() {
    var current = arguments[0];
    for(var i = 1; i < arguments.length; i++) {
        if(current[arguments[i]]) {
            current = current[arguments[i]];
        } else {
            return null;
        }
    }
    return current;
}

var arr = { foo : 1, bar: { baz : 2 }, bee : 3 };
var baz = getter(arr, 'bar', 'baz');

Upvotes: 1

Aaron Qian
Aaron Qian

Reputation: 4505

How about change the getter function signature as getter('bar', 'baz') instead

function getter() {
  var v = arr;
  for(var i=0; i< arguments.length; i++) {
    if(!v) return null;
    v = v[arguments[i]];
  }
  return v;
}

ps. didn't test, but you get the idea ;)

Upvotes: 13

Related Questions