How to get a nested property for an object dynamically? - javascript

I need to create a function that search a property in an object and returns its value.
Object could have an arbitrary structure with property nested in other objects.
How could I change my script?
var item = {
id: 10,
properties: {
parent_id: 20,
x: {
y: 100
}
}
}
function getValue(what) {
console.log(item[what]);
}
getValue('id');
// ok return 10
getValue('properties.parent_id')
// undefined - ISSUE here I would like to have returned 20

You can provide a syntax to access these properties in the getValue function parameter. For example, to access properties.parent_id you can use 'properties.parent_id'.
Then the getValue function should be written as the following:
function getValue(prop) {
if (typeof(prop) !== 'string')
throw 'invalid input string';
props = prop.split('.');
var value = item[props[0]];
for(var i = 1, l = props.length; i < l; i++) {
value = value[props[i]];
}
return value;
}
Example:
getValue('properties.parent_id'); //returns 20

You need to create a "path", i.e. a sequence, of keys too access in order. One way is to choose an uncommong separator that is never going to be used for object keys, e.g. |:
element = obj;
path.split("|").forEach(function(key){
element = element[key];
});
if you cannot exclude any char from the keys then supporting escaping is mandatory; for example you could use , to separate keys but allowing #, to mean a comma is part of the key and ## meaning an at-sign is part of the key.
element = obj;
(path+",").match(/([^#,]|#.)*,/g).forEach(function(x){
element = element[x.slice(0,-1).replace(/#(.)/g, "$1")];
});
for example the path "1,2,x,y#,z,,w##" can be used to access
obj[1][2].x["y,z"][""]["w#"]

The code below makes flat obj so to access like that.
var flatten = function(obj,into,prefix){
into = into ||{};
prefix = prefix || '';
_.each(obj,function(val,key){
if(obj.hasOwnProperty(key)){
if(val && typeof val === 'object'){
flatten(val,into,prefix + key + '.');
}else{
into[prefix + key] = val;
}
}
});
return into;
};
The working JSFiddle is here http://jsfiddle.net/fper2d73/
The complete code is
var item = {
id: 10,
properties: {
parent_id: 20,
x: {
y: 100
}
}
}
var flatten = function(obj,into,prefix){
into = into ||{};
prefix = prefix || '';
_.each(obj,function(val,key){
if(obj.hasOwnProperty(key)){
if(val && typeof val === 'object'){
flatten(val,into,prefix + key + '.');
}else{
into[prefix + key] = val;
}
}
});
return into;
};
var _item = flatten(item);
function getValue(what) {
console.log(_item[what]);
}
getValue('id');
// returns 10
getValue('properties.parent_id')
// returns 20
getValue('properties.x.y')
//returns 100

for a deeply nested object you can use a recursive function to retrieve all the object which are nested inside parent Object.It can be applied to an object literal having three to more number of nested object
var parentObj = {
parentProp: 10,
childObj: {
childProp: 20,
grandChildObj: {
y: {
z:'lol',
places:['newyork','canada','dhaka']
}
}
}
}
var arr=[];
var container=[];
container.push(parentObj);
var count=0;
function getObjNum(obj){ //<= recursive function to retrieve all the nested objects inside parent object
var prop=Object.getOwnPropertyNames(obj);
for(i=0;i<prop.length;i++){
if(typeof(obj[prop[i]])=='object'){
if(!Array.isArray(obj[prop[i]])){
container.push(obj[prop[i]]);
count++;
getObjNum(obj[prop[i]]); // recursive call to getObjNum
}
}
}
}
getObjNum(parentObj); // sent the parent object to getObjNum
function getVal(str){
var split=str.split('.');
container.forEach(function(obj){
if(obj.hasOwnProperty(split[split.length-1])){
console.log(obj[split[split.length-1]]);
}
});
}
getVal('parentObj.parentProp');
getVal('y.z');

Related

Shorthand to manipulate an object attribute and reassign

I have a deeply nested object and I want to manipulate a value of it and reassign it again. Is there a shorthand way for this other than writing it all out again or assigning it to a variable:
createStops[idx]['place']['create'][stop][key][value] = createStops[idx]['place']['create'][stop][key][value].toString()
looks ugly doesn't it? Something like:
createStops[idx]['place']['create'][stop][key][value].toStringAndReassign()
but JS built in.
Edit: In my case it is a number, if it's for your case too please check out #MarkMeyer answer.
No, there isn't.
Assigning a new value requires an assignment.
Strings are immutable, so you can't convert an existing value into a string in-place.
Given a value that's a number, if you just want it to be a string, you can coerce to a string with an assignment operator:
let o = {
akey: {
akey:{
value: 15
}
}
}
o.akey.akey.value += ''
console.log(o)
No,
Going to the same index is needed to store the value
Although it is not possible as mentioned by #Quentin you can define a custom getter in your object like:
var foo = {
a: 5,
b: 6,
get c () {
return this.b.toString()+' text'
}
};
console.log(foo.c);
You're not reassigning the value as you are semantically formatting your values. In order to format your value you are mutating your initial object. If you do not pretend to modify an object for formatting purposes that will work just fine.
You do not have integrated functions to use like that, but you could use of some utilitary functions of your own to help you manage assignements and make it less verbal.
SPOIL : The final use look like
// call the function to do +1 at the specified key
executeFunctionAtKey(
// The object to change the value on
createStops,
// The path
`${idx}.place.create.${stop}.${key}.${value}`,
// The thing to do
(x) => x + 1,
);
const createStops = {
idx: {
place: {
create: {
stop: {
key: {
value: 5,
},
},
},
},
},
};
const idx = 'idx';
const stop = 'stop';
const key = 'key';
const value = 'value';
// Function that go to the specified key and
// execute a function on it.
// The new value is the result of the func
// You can do your toString there, or anything else
function executeFunctionAtKey(obj, path, func) {
const keys = path.split('.');
if (keys.length === 1) {
obj[path] = func(obj[key]);
return obj;
}
const lastPtr = keys.slice(0, keys.length - 1).reduce((tmp, x) => tmp[x], obj);
lastPtr[keys[keys.length - 1]] = func(lastPtr[keys[keys.length - 1]]);
return obj;
}
// call the function to do +1 at the specified key
executeFunctionAtKey(
// The object to change the value on
createStops,
// The path
`${idx}.place.create.${stop}.${key}.${value}`,
// The thing to do
(x) => x + 1,
);
console.log(createStops);
with the toString example from Number to String
const createStops = {
idx: {
place: {
create: {
stop: {
key: {
value: 5,
},
},
},
},
},
};
const idx = 'idx';
const stop = 'stop';
const key = 'key';
const value = 'value';
// Function that go to the specified key and
// execute a function on it.
// The new value is the result of the func
// You can do your toString there, or anything else
function executeFunctionAtKey(obj, path, func) {
const keys = path.split('.');
if (keys.length === 1) {
obj[path] = func(obj[key]);
return obj;
}
const lastPtr = keys.slice(0, keys.length - 1).reduce((tmp, x) => tmp[x], obj);
lastPtr[keys[keys.length - 1]] = func(lastPtr[keys[keys.length - 1]]);
return obj;
}
// call the function to do +1 at the specified key
executeFunctionAtKey(
// The object to change the value on
createStops,
// The path
`${idx}.place.create.${stop}.${key}.${value}`,
// The thing to do
(x) => x.toString(),
);
console.log(createStops);
Theoretically you could build a function that takes an object, a path and the property to set it to.
This will reduce the readability of your code, so i would advice using ordinary assignment. But if you need it check out the snippet below:
//
function setProp(object, path, val) {
var parts = path.split("/").filter(function (p) { return p.length > 0; });
var pathIndex = 0;
var currentTarget = object;
while (pathIndex < parts.length - 1) {
currentTarget = currentTarget[parts[pathIndex]];
pathIndex++;
}
if (val instanceof Function) {
currentTarget[parts[pathIndex]] = val(currentTarget[parts[pathIndex]]);
}
else {
currentTarget[parts[pathIndex]] = val;
}
return object;
}
var createStops = {
idx: {
place: {
create: {
stop: {
key: {
value: 5
}
}
}
}
}
};
function toString(p) { return p.toString(); }
console.log(JSON.stringify(createStops, null, 4));
setProp(createStops, 'idx/place/create/stop/key/value', toString);
console.log(JSON.stringify(createStops, null, 4));
UPDATE 1
Allowed passing functions and used OP JSON structure for snippet

Setting the value of a nested array element based on a dynamic list of keys?

I have a dynamically-generated object like so (I'm just noting the 'children' array keys here for display purposes, assume its an otherwise syntactically sound array):
foo: {
children: [
0: {
children: [
3: {
children: [
6: {
//...etc
I then have a list of keys being generated:
var keys = [0,3,6];
And I need to set the value of the element of the array described by the list of keys, as such:
foo.children[0].children[3].children[6] = "bar";
Any ideas? I've tried a few different recursive techniques, but I'm missing something somewhere.
While you could do this recursively, I think it is more efficient to do it in a loop like this:
function setNestedChild( obj, path, value ){
var child = obj;
path.forEach(function( i, idx ){
if( idx == path.length - 1 ){
child.children[ i ] = value;
}
else {
child = child.children[ i ];
}
});
}
How about a method along the lines of
def function getElement (keys) {
var el = this.foo
for (i = 0; i < keys.length; i++) {
el = el.children[keys[i]]
}
return el
}
This function is just to retrieve the desired element doesn't actually set the value.
How about this?
function setVal(obj, keys, val){
var temp = obj;
while(keys.length){
var i = keys.pop();
if(!keys.length) return temp.children[i] = val;
temp = temp.children[i];
}
}

Easy way to set javascript object multilevel property?

I am trying to create a javascript object like
var allUserExpiry={};
allUserExpiry[aData.userId][aData.courseId][aData.uscId] = aData;
But I am getting an error like allUserExpiry[aData.userId] undefined.
Is there a way, whereby I can set multi-level JS-Object keys? or is it important that I should go by doing allUserExpiry[aData.userId]={}, then allUserExpiry[aData.userId][aData.courseId]={} ?
Please let me know if there are any utility functions available for the same.
No, there is no way to set "multilevel keys". You need to initialize each object before trying to add properties to it.
var allUserExpiry = {};
allUserExpiry[aData.userId] = {}
allUserExpiry[aData.userId][aData.courseId] = {}
allUserExpiry[aData.userId][aData.courseId][aData.uscId] = aData;
Using Computed property names from ES6, it is possible to do:
var allUserExpiry = {
[aData.userId] = {
[aData.courseId]: {
[aData.uscId]: aData
}
}
};
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names
Simply use loadash,
let object = {};
let property = "a.b.c";
let value = 1;
_.set(object, property, value); // sets property based on path
let value = _.get(object, property, default); // gets property based on path
Or you can do it:
function setByPath(obj, path, value) {
var parts = path.split('.');
var o = obj;
if (parts.length > 1) {
for (var i = 0; i < parts.length - 1; i++) {
if (!o[parts[i]])
o[parts[i]] = {};
o = o[parts[i]];
}
}
o[parts[parts.length - 1]] = value;
}
And use:
setByPath(obj, 'path.path2.path', someValue);
This approach has many weak places, but for fun... :)
Why not just do this?
var allUserExpiry={};
allUserExpiry[aData.userId] = {aData.courseId: {aData.uscId: aData}};
I have a pretty hacky but short way of doing it in IE9+ as well as real browsers.
Given var path = 'aaa.bbb.ccc.ddd.eee'; where path is what your intending to make into an object and var result = {}; will will create the object {aaa: {bbb: {ccc: {ddd: {eee: {}}}}}
result = {}
path.split('.').reduce(function(prev, e) {
var newObj = {};
prev[e] = newObj;
return newObj;
}, result);
will store the object in result.
How it works:
split('.') converts the input into ['aaa', 'bbb', 'ccc', 'ddd', 'eee']
reduce(function (...) {...}, result) runs through the array created by split, and for each entry will pass along a returned value to the next one. In our case we pass the new object through after adding the new object to the old one. This creates a chain of objects. reduce returns the last object you return inside of it, so we have to defined result beforehand.
This relies on using references so it won't be immediately clear how it works if you're expecting your code to be maintained by anyone else and should probably be avoided to be honest, but it works at least.
You can also use the following to create the initial structure:
var x = function(obj, keys) {
if (!obj) return;
var i, t;
for (i = 0; i < keys.length; i++) {
if (!t) {
t = obj[keys[i]] = {};
} else {
t[keys[i]] = {};
t = t[keys[i]];
}
}
};
var a = {};
x(a, ['A', 'B', 'C', 'D', 'E', 'F']);
Another approach without strings or array as argument.
function fillObject() {
var o = arguments[0];
for(var i = 1; i < arguments.length-1; i++) {
if(!o.hasOwnProperty(arguments[i])) {
o[arguments[i]] = {};
}
if(i < arguments.length-2) {
o = o[arguments[i]];
}else {
o[arguments[i]] = arguments[i+1]
}
}
}
var myObj = {"foo":{}};
fillObject(myObj,"back","to","the","future",2);
console.log(JSON.stringify(myObj));
// {"foo":{},"back":{"to":{"the":{"future":2}}}}
But I wouldn't use it :-) It's just for fun.
Because I don't like too much intelligent algorithm. (If it was in this category)
Using lodash you can do this easily (node exists and empty check for that node)..
var lodash = require('lodash-contrib');
function invalidateRequest(obj, param) {
var valid = true;
param.forEach(function(val) {
if(!lodash.hasPath(obj, val)) {
valid = false;
} else {
if(lodash.getPath(obj, val) == null || lodash.getPath(obj, val) == undefined || lodash.getPath(obj, val) == '') {
valid = false;
}
}
});
return valid;
}
Usage:
leaveDetails = {
"startDay": 1414998000000,
"endDay": 1415084400000,
"test": { "test1" : 1234 }
};
var validate;
validate = invalidateRequest(leaveDetails, ['startDay', 'endDay', 'test.test1']);
it will return boolean.
Another solution using reduce function (thanks Brian K).
Here we created a get/set to general proposes. The first function return the value in any level. The key is splited considering the separator. the function return the value refered from last index in the key's array
The second function will set the new value considering the last index of the splited key
the code:
function getObjectMultiLevelValue(_array,key,separator){
key = key.split(separator || '.');
var _value = JSON.parse(JSON.stringify(_array));
for(var ki in key){
_value = _value[key[ki]];
}
return _value;
}
function setObjectMultiLevelValue(_array,key,value,forcemode,separator){
key.split(separator || '.').reduce(function(prev, currKey, currIndex,keysArr) {
var newObj = {};
if(prev[currKey] && !forcemode){
newObj = prev[currKey];
}
if(keysArr[keysArr.length-1] == currKey){
newObj = value;
prev[currKey] = newObj;
}
prev[currKey] = newObj;
return newObj;
}, _array);
return _array;
}
//testing the function
//creating an array
var _someArray = {a:'a',b:'b',c:{c1:'c1',c2:{c21:'nothing here...'}}};
//a multilevel key to test
var _key = 'a,a1,a21';
//any value
var _value = 'new foo in a21 key forcing replace old path';
//here the new value will be inserted even if the path exists (forcemode=true). Using comma separator
setObjectMultiLevelValue(_someArray,_key,_value,true,',');
console.log('_someArray:');
console.log(JSON.stringify(_someArray));
//inserting another value in another key... using default separator
_key = 'c.c2.c21';
_value = 'new foo in c21 key';
setObjectMultiLevelValue(_someArray,_key,_value);
console.log('_someArray:');
console.log(JSON.stringify(_someArray));
//recovering the saved value with different separators
_key = 'a,a1,a21';
console.log(getObjectMultiLevelValue(_someArray,_key,','));
_key = 'c.c2.c21';
console.log(getObjectMultiLevelValue(_someArray,_key));
Let assume our object is
const data = {
//some other data
userInfo: {},
};
First, define a new property of that object
data.userInfo.vehicle = {};
then simply
data.userInfo.vehicle.vehicleType = state.userInfo.vehicleType;

JS reference multi-layered dynamic object string

Suppose I have a string that references a deep Javascript object, such as:
var string = 'response.basicInfo.gender';
I want to build a function that safely checks if that object exists, by splitting the string by .s and building the object, checking each level as it goes, and then dealing with the value of the object, if it actually exists.
var parseHelper = function(response, items) {
for (item in items) {
var parts = String(item).split('.');
for (var i = 0; i < parts.length; ++i) {
// ... etc. build the string
}
}
}
parseHelper(response, {
'basicInfo.gender': function(val){
return (val == 'M') ? 'Male' : (val == 'F') ? 'Female' : val;
},
})
While the above function is incomplete, let's suppose we use it to build the string and check if each exists:
// so let's suppose we build the string:
var builtString = "response['basicInfo']";
// Now we want to check if it exists
if (response['basicInfo']) {
// And if we are fine that it exists, go on to the next item
var builtString = "response['basicInfo']['gender']";
// etc.
}
I don't have a problem building that function, I just don't know how to evaluate a string like "response['basicInfo']['gender']" and turn it into an actual reference to the object. My only guess would be eval(), but eval is evil...
Update
I know you can reference a global object by going window['blah'], but this response object I want to reference is not in the global scope, so do I use this? And even if I can do this, how do I reference it with multiple layers?
Plus 1 to Bergi, who linked to a page with six links, one of which had an answer I adapted to solve the problem:
Convert JavaScript string in dot notation into an object reference
Here's the full solution.
// We want to run a parse function to convert
// the object response.basicInfo.gender (which is 'M')
// to 'Male', etc.
// Sets the value of a string representing a deep object.
setDeep: function(root, path, value) {
var parts = path.split('.'), obj = root;
for (var i = 0; i < parts.length - 1; ++i) {
obj = obj[parts[i]] || { };
}
obj[parts[parts.length - 1]] = value;
return obj;
},
// Gets the value of a string representing a deep object.
getDeep: function(root, path) {
var parts = path.split('.'), obj = root, target;
for (var i = 0; i < parts.length; ++i) {
target = obj[parts[i]];
if (typeof target == "undefined") return void 0;
obj = target;
}
return obj;
},
// Loops through multiple string representations
// of deep objects and runs the values through
// assigned parsing functions for each of them,
// returning the root object.
parseHelper: function(obj, items) {
for (item in items) {
var val = getDeep(obj, item);
var func = items[item];
if (val !== undefined) {
val = func(val);
}
setDeep(obj, item, val);
}
return obj;
},
// response.basicInfo.gender = 'M';
// response.foo.bar = true;
response = parseHelper(response, {
'basicInfo.gender': function(val){
return (val == 'M') ? 'Male' : (val == 'F') ? 'Female' : val;
},
'foo.bar': function(val) {
return (val) ? false : true;
},
});
// response.basicInfo.gender = 'Male';
// response.foo.bar = false;

Object nested property access

I'm trying to write a function that adds an accessor for each nested property in an object. To make this a bit clearer, given object o, and a string representing a path, I should be able to access the property at that path as a named property:
var o = {
child1: "foo",
child2: {
child1: "bar",
child2: 1
child3: {
child1: "baz"
}
}
};
addAccessors(o);
o["child2.child1"]; // "bar"
o["child2.child2"]; // 1
o["child2.child3.child1"]; // "baz"
Note that the names won't always be as uniform.
Here is what I have so far:
function addAccessors(parent) {
function nestedProps(o, level) {
if (typeof o == "object") {
var level = level || "";
for (p in o) {
if (o.hasOwnProperty(p)) {
if (level && typeof(o[p]) != "object") {
parent[level + "." + p] = o[p];
}
nestedProps(o[p], (level ? level + "." : "") + p);
}
}
}
}
nestedProps(parent);
}
As you can see from this line: obj[level + "." + p] = o[p];, I am simply adding the values as new properties onto the array.
What I would like to be able to do is add an accessor that retrieves the value from the appropriate property, so that it is "live". To refer to my earlier example:
o["child2.child2"]; // 1
o["child2"]["child2"] = 2;
o["child2.child2"]; // Still 1, but I want it to be updated
Any ideas on how I can accomplish this?
This is not possible with browsers that are in use nowadays. There is no way to assign a callback or similar to the assignment. Instead use a function to fetch the value in real time:
o.get=function(path)
{
var value=this;
var list=path.split(".");
for (var i=0; i<list.length; i++)
{
value=value[list[i]];
if (value===undefined) return undefined;
}
return value;
}
o.get("child2.child1");

Categories