Set JSON property with fully qualified string - javascript

This question achieves kinda the opposite of what I'm trying to do. Basically, I have this object:
var a = {
b: {
c: 'Foo'
}
}
What I need to do is set the value of c given the string 'b.c'. Unfortunately, I can't do this:
a['b.c'] = 'Bar'
As far as I can tell, the question above doesn't get me anywhere close as it just copies the values of the object properties so they can be read. It doesn't help me set the values of the object properties, however. Here's what I have so far:
var key = 'b.c'.split('.');
for (var i = 0; i < key.length; i++) {
// do something
}

Here's a functional way, is this what you need? It doesn't use the particular a[string] syntax but a function where you can pass the object string and the value to set:
var obj = { foo: { bar: { lol: { lulz: 69 } } } };
function setProp(obj, prop, value) {
var props = prop.split('.');
return [obj].concat(props).reduce(function(a,b,i) {
return i == props.length ? a[b] = value : a[b];
});
}
setProp(obj, 'foo.bar.lol.lulz', 'hello world');
console.log(obj.foo.bar.lol.lulz); //=> "hello world"

You have to intercept the last iteration of the loop, and from there assign instead of redefining your temp variable.
I adapted the answer to the question you linked to assign the value 2 to a.b.c:
var a = {
"b" : {
"c" : 1
}
}
var n = "b.c".split(".");
var x = a;
for(var i = 0; i < n.length; i++){
if(i == n.length-1) {
x[n[i]] = 2;
} else {
x = x[n[i]];
}
}
http://jsfiddle.net/Fuhbb/
This is pretty much what #Ian is doing in his jsfiddle. Intead of an if, he loops one step less, then deals with the assignment after the loop.

For the record, I eventually figured out another way to do this:
function setProperty(obj, props, val) {
if (obj[props[0]] instanceof Object) {
setProperty(obj[props[0]], props.slice(1, props.length), val);
return;
}
obj[props[0]] = val;
}
Call it like this:
a = {
b: {
c: 'Foo'
}
};
var whatever = 'b.c';
var props = whatever.split('.');
setProperty(a, props, 'Bar');

Related

Change variable with string instead of just accessing it

I can access a variable easily:
function accessVariable(accessFunction, variable) {
var namespaces = accessFunction.split(".");
for (var b = 0; b < namespaces.length; b++) {
if (namespaces[b] != "") {
try {
variable = variable[namespaces[b]];
} catch {
variable = undefined;
};
};
return variable;
};
But updating this gotten variable is something that I don't know how to do.
It's easiest if you use a separate function to update. This can take the new value as another argument.
Then you stop the iteration before the last item in namespace, and use that as the index to assign to.
function updateVariable(accessFunction, variable, value) {
var namespaces = accessFunction.split(".");
for (var b = 0; b < namespaces.length - 1; b++) {
if (namespaces[b] != "") {
try {
variable = variable[namespaces[b]];
} catch {
variable = undefined;
}
}
}
if (variable) {
variable[namespaces.pop()] = value;
}
}
let obj = {a: {b:[{c: 10}]}};
updateVariable('a.b.0.c', obj, 20);
console.log(obj);
Define a single global-scoped variable, and put your variables there.
var Glob = {}; // globally scoped object
function changeVar(){
Glob.variable1 = 'value1';
}
function SeeVar(){
alert(Glob.variable1); // shows 'value1'
}

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;

How to update a var in nested objects

Say I have an object and I want to set a variable deep nested inside this object, but the var does not yet exist. What's the best way to do this? If I for example have a string that shows where the variable to be updated should be, like below.
var myObject = {};
var location = "myObject.test.myVar";
var value = "My value!";
setValue(location, value, myObject);
I want this to result in:
myObject = {
test: {
myVar: "My value!"
}
};
And location can be much deeper than that.
Any help is appreciated!
Thanks!
Andreas
This function will do what you want.
Note that it changes the object by reference.
Note it ignores the first name as it's the object itself.
function setValue(location, value, object)
{
var path = location.split(".");
var current = object;
for (var i = 1; i < path.length; i++)
{
if ((i + 1) == path.length)
current[path[i]] = value;
else
{
current[path[i]] = {};
current = current[path[i]];
}
}
}
var myObject = {};
var location = "test.my.deep.hidden.nested.myVar";
var otherLoc = "test.my.deep.secret.var";
var value = "My value!";
function setValue(location, value, obj){
var i, prev = obj, curr;
location = location.split(".");
for(i = 0; i < location.length - 1; ++i){
curr = prev[location[i]];
if("object" !== typeof curr){
prev[location[i]] = {}
curr = prev[location[i]];
}
prev = curr;
}
curr[location[i]] = value;
}
setValue(location, value, myObject);
setValue(otherLoc, 42, myObject);
console.log(JSON.stringify(myObject));
Result:
{
"test": {
"my": {
"deep": {
"hidden": {
"nested": {
"myVar": "My value!"
}
},
"secret": {
"var":42
}
}
}
}
}
Note that you might want to add some features, like checking whether the location is actually valid ("this.is..invalid").
You can simply access object inside another object by Dot(.)
So in your case, you are specifying the expression in quotes. You can try this out
var location = myObject.test.myVar; //Without quotes
and similarly if you have more nested objects.

Reaching into object from array in javascript?

How can I reach into a object using an array and set a value - preferably without using eval, doing something like object[eval(["key", "deepkey"].split("")) = "newvalue"?
Doing it manually, I would just do object.key.deepkey = "newvalue", but again, I need to do this using an array to reach into the right property.
The object for reference:
object = {
key: {
deepKey: "value"
}
}
You can use a recursive function to step through each level of the array (or object) like so:
function val(array, indices) {
if(indices.length > 1) {
var idx = indices.shift();
return val(array[idx], indices);
}
else {
return array[indices.shift()];
}
}
var obj = { a: { b: 'c' } };
//result is 'c'
var result = val(obj, ['a', 'b']);
If you want to get an object reference, simply specify the second arg only up to that:
var obj = {
a: {
b: {
c: 'foo'
}
}
};
var ref = val(obj, ['a', 'b']);
//ref is now obj.a.b, so you can do something like...
ref.x = 'bar';
console.dir(ref); //outputs something like { c: 'foo', x: 'bar' }
You can write array type syntax as. jsfiddle
object = {
key: {
deepKey: "value"
}
}
object['key']['deepkey']='newvalue'
if you have keys in array you can do this
var keys = ['key','deepkey'];
var obj = object;
for(var k =0; k <keys.length-1; k++){
obj= obj[keys[k]];
}
obj[keys[k]] = 'newvalue'
You can take the function from this question and rework it to access properties of an object.
http://jsfiddle.net/jbabey/Mu4rP/
var getPropByName = function (propName, context) {
var namespaces = propName.split('.');
for(var i = 0; i < namespaces.length; i++) {
context = context[namespaces[i]];
}
return context;
};
var myObject = {
someKey: {
deepKey: "value"
}
};
myObject.someKey.deepKey; // "value"
getPropByName('someKey.deepKey', myObject); "value"
An alternative could be using Array.map this way:
function deepkey(obj,keys,set){
var i=1
,kys = keys.split('.')
,exist = kys.map( function(k){
var prev = this[i-1], isobj = prev.constructor === Object;
this.push( isobj && k in prev ? prev[k] : prev);
return (i++,this[i-1]);},
[obj]
)
,x = exist[exist.length-2];
if (x && x.constructor === Object && set){
x[kys[kys.length-1]] = set;
}
return x[kys.pop()] || null;
}
// usage
var obj = { a:{ b:{ c:1, cc:{ d:{ e:{ a:1,b:2,c:3 } } } } } };
// assign [1,2,3,4,5] to obj.a.b.cc.d.e.b
console.log(deepkey(obj,'a.b.cc.d.e.b',[1,2,3,4,5])); //=> [1,2,3,4,5]
// get obj.a.b.cc.d.e.b[2]
console.log(deepkey(obj,'a.b.cc.d.e.b')[2]); //=> 3
// get non existing path obj.a.b.c.d.e.b
console.log(deepkey(obj,'a.b.c.d.e.b')); //=> null

What's the fastest way to interpret a nested javascript object attribute accessor string?

Say I have a nested object structure like:
var o = { a: { b: { c: 1 } } };
and I have a an accessor String like "a.b.c".
What's the fastest function to return the specified nested value (to any depth [1..n])?
I.e. in this case, getNested(o, 'a.b.c') === 1 and getNested(o, 'a') === {b:{c:1}}.
What's the best implementation of getNested?
one more variant:
function getNested(obj, path) {
path.replace(/[^\.]+/g, function (p) {
obj = obj[p];
});
return obj;
}
I hate to give an answer without profiling the code myself, but given that eval is probably slow, and forEach is slower than just running through a for-loop, I would start with:
// precondtion: path is a nonempty string
function getNested(obj, path) {
var fields = path.split(".");
var result = obj;
for (var i = 0, n = fields.length; i < n; i++) {
result = result[fields[i]];
}
return result;
}
But I would test this against other approaches.
I don't think that trying to optimize away the array construction on split would be worthwhile, but this is only one thing to try if you are interested in the fastest way.
ADDENDUM
Here is a transcript so you can see it in action:
$ node
> function getNested(obj, path) {
... var fields = path.split(".");
... var result = obj;
... for (var i = 0, n = fields.length; i < n; i++) {
... result = result[fields[i]];
... }
... return result;
... }
> var o = { a: { b: { c: 1 } } };
> getNested(o, "a")
{ b: { c: 1 } }
> getNested(o, "a.b.c")
1
** ADDENDUM 2 **
So embarassing -- I forgot the var in front of result before. That might speed it up a bit!
Other things to try:
Forget about the "optimization" with n and just do the for-loop test with i < test.length (might be optimized away anyway)
Replace split with substrings and indexOfs
Do the split with a regex /\./ instead of a raw string "."
Maybe something like this:
function getNested(o, path) {
var parts = path.split('.');
var member = o;
while (member && parts.length) {
member = member[parts.shift()];
}
return member;
}
It's probably not the fastest possible solution, but might be a useful starting point.
try{
var prop=eval(string);
}
catch(er){
prop=undefined;
}
My approach
var o = { a: { b: { c: 1 } } };
var getNested = function(p) {
var t;
p.split('.').forEach(function(e) {
t = o[e] || t[e]
});
return t
}
you can try:
getNested('a.b.c')

Categories