gen_Eric
gen_Eric

Reputation: 227240

Set value of object using a string as the key

Let's say I have a string representing the keys in an object. Here's an example:

var obj = {
    test: 12,
    high: {
      sky: {
        val: 14
      }
    },
    low: [1, 2, 3]
},
keys = 'high.sky.val';

So, I want to set the value of obj.high.sky.val (with 'high.sky.val' being in a string).

I know how to read the value (though, this may not be the best way):

var keyPieces = keys.split('.'), value = obj;
keyPieces.forEach(function(x){
   value = value[x];
});
console.log(value); // 14

I can't figure out how to set obj.high.sky.val (without using eval).

How can I set the value of a property in an object, if that key is a string?

Upvotes: 1

Views: 5735

Answers (5)

Hjalmar Snoep
Hjalmar Snoep

Reputation: 311

I think this is an important question and I needed a variant, that allows for arrays nested in the objects AND for deep creation of non-existant fields. I know you specified 'object' but in reality an object can also hold arrays. In this situation the otherwise excellent code by @Marshall didn't help me out. So I started fiddling and got the following code (hope this helps anyone):

function objGet(obj, keyString) {
    // allow for arrays, returns undefined for non-existant-fields.
    var keys=[{label:"",type:"field",is_array:false}], current_key=0;
    for(var i=0;i<keyString.length;i++)
{
        var c=keyString.charAt(i);
        switch(c)
        {
            case ".":
                current_key++;
                keys[current_key]={label:"",type:"field",is_array:false};
            break;
            case "[":
                keys[current_key].is_array=true;
                current_key++;
                keys[current_key]={label:"",type:"index",is_array:false};
            break;
            case "]": 
            break;
            default:
                keys[current_key].label+=c;
        }
    }
    var part=obj;
    for(i = 0; i < keys.length; i++) 
    {
        var label=keys[i].label;
        if(i==keys.length-1)
        {
            return part[label];
            }else{
            if(part[label] === undefined)
            {
                    return undefined;
                }
                part = part[label];
            }
        }
    }

function objSet(obj, keyString, val) {
// allows for arrays, deep creates non-existant fields.
    var keys=[{label:"",type:"field",is_array:false}], current_key=0;
    for(var i=0;i<keyString.length;i++)
    {
        var c=keyString.charAt(i);
        switch(c)
        {
            case ".":
                current_key++;
                keys[current_key]={label:"",type:"field",is_array:false};
            break;
            case "[":
                keys[current_key].is_array=true;
                current_key++;
                keys[current_key]={label:"",type:"index",is_array:false};
            break;
        case "]": 
        break;
            default:
            keys[current_key].label+=c;
        }
    }
    var part=obj;
    for(i = 0; i < keys.length; i++) 
    {
        var label=keys[i].label;
        if(i==keys.length-1)
        {
            part[label] = val;
    }else{
        if(part[label] === undefined)
        {
            // we need to create it for deep set!
            if(keys[i].is_array)
            {
                part[label]=[];
            }else{
                part[label]={};
            }
        }
            part = part[label];
        }
    }
}

// TESTS
var obj = {
    test: 12,
    high: {
        sky: {
            val: 14
        }
    },
    kneedeep: [
                {something:"som0",something_else:"elze0"},
                {something:"som1",something_else:"elze1"}
              ],
    low: [1, 2, 3]
};
var obj_str=JSON.stringify(obj);
console.log("testing with object: "+obj_str);
 // TEST GET
console.log("test: "+objGet(obj, 'test')); // returns 999
console.log("high.sky.non.existant: "+objGet(obj, 'high.sky.non.existant')); // returns undefined
console.log("kneedeep[0].something: "+objGet(obj, 'kneedeep[0].something')); // returns "som0"
console.log("kneedeep[1].something_else: "+objGet(obj, 'kneedeep[1].something_else')); // returns "elze1"
console.log("high.sky.val: "+objGet(obj, 'high.sky.val')); // returns 14
console.log("low[0]: "+objGet(obj, 'low[0]')); // returns  1


  // TEST SET

objSet(obj, 'test', 999); // return 999
console.log("result SET 'test', 999:");
console.log(JSON.stringify(obj));

obj=JSON.parse(obj_str); // reset the object
objSet(obj, 'high.sky.non.existant', 1234); // creates the necessary objects.
console.log("result SET 'high.sky.non.existant', 1234:");
console.log(JSON.stringify(obj));

obj=JSON.parse(obj_str); // reset the object
objSet(obj, 'high.sky.val', 111); 
console.log("result SET 'high.sky.val', 111:");
console.log(JSON.stringify(obj));

obj=JSON.parse(obj_str); // reset the object
objSet(obj, 'kneedeep[0].something', 111); 
console.log("result SET 'kneedeep[0].something', 111:");
console.log(JSON.stringify(obj));

obj=JSON.parse(obj_str); // reset the object
objSet(obj, 'kneedeep[1].something_else', 1234); 
console.log("result SET 'kneedeep[1].something_else', 1234:");
console.log(JSON.stringify(obj));

Upvotes: 0

Mike Samuel
Mike Samuel

Reputation: 120516

function setDeep(el, key, value) {
    key = key.split('.');
    var i = 0, n = key.length;
    for (; i < n-1; ++i) {
        el = el[key[i]];
    }
    return el[key[i]] = value;
}

function getDeep(el, key) {
    key = key.split('.');
    var i = 0, n = key.length;
    for (; i < n; ++i) {
        el = el[key[i]];
    }
    return el;
}

and you can use it thus:

setDeep(obj, 'high.sky.val', newValue);

Upvotes: 2

Dagg Nabbit
Dagg Nabbit

Reputation: 76736

Just for fun:

function setKey(key, value, targetObject) {
  var keys = key.split('.'), obj = targetObject || window, keyPart;
  while ((keyPart = keys.shift()) && keys.length) {
    obj = obj[keyPart];
  }
  obj[keyPart] = value;
}

Edit: The previous version wouldn't have worked with "no-dot" keys... Fixed.

Upvotes: 6

Marshall
Marshall

Reputation: 4766

You can use a pair of functions to set and get values. I just threw together an example.

objGet takes an object and key string. It will attempt to get the value. If it can't find it it will return undefined.

objSet takes an object, key string and value. It will attempt to find and set the value. If it can't (because of a bad key string) it will return undefined. Else it returns the value passed.

function objGet(obj, keyString) {
    for(var keys = keyString.split('.'), i = 0, l = keys.length; i < l; i++) {
        obj = obj[keys[i]];
        if(obj === undefined) return undefined;
    }
    return obj;
}

function objSet(obj, keyString, val) {
    for(var keys = keyString.split('.'), i = 0, l = keys.length; i < l - 1; i++) {
        obj = obj[keys[i]];
        if(obj === undefined) return undefined;
    }
    if(obj[keys[l - 1]] === undefined) return undefined;
    obj[keys[l - 1]] = val;
    return val;
}


//// TESTING

var obj = {
    test: 12,
    high: {
        sky: {
            val: 14
        }
    },
    low: [1, 2, 3]
};

objGet(obj, 'test'); // returns 12
objGet(obj, 'high.sky.val'); // returns 14
objGet(obj, 'high.sky.non.existant'); // returns undefined

objSet(obj, 'test', 999); // return 999
obj.test; // 999

objSet(obj, 'high.sky.non.existant', 1234); // returns undefined
obj.high.sky; // { val: 14 }

objSet(obj, 'high.sky.val', 111); // returns 111
obj.high.sky; // { val: 111 }

Upvotes: 2

Niet the Dark Absol
Niet the Dark Absol

Reputation: 324640

I actually had to make a couple of functions to achieve this end when working in GameMaker:HTML5

function js_get(varname) {
    if( varname.indexOf(".") < 0)
        return window[varname];
    else {
        var curr = window, steps = varname.split("."), next;
        while(next = steps.shift()) curr = curr[next];
        return curr;
    }
}
function js_set(varname,value) {
    if( varname.indexOf(".") < 0)
        window[varname] = value;
    else {
        var curr = window, steps = varname.split("."), next, last = steps.pop();
        while(next = steps.shift()) curr = curr[next];
        curr[last] = value;
    }
}

This works because objects are passed by reference in JS.

Upvotes: 5

Related Questions