开发者

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

var arr = { foo : 1, bar: { baz : 2 }, b开发者_C百科ee : 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.


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.


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 


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


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


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


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'


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.


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


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


 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


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.


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


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


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜