1

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?

5 Answers 5

6

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.

Sign up to request clarification or add additional context in comments.

2 Comments

How do I tell it what object to use?
Hang on, editing. I assumed you wanted to start resolving from the global object... there you go, now it can optionally have a target object (otherwise it defaults to window). If you don't want the window default, just remove " || window".
5

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.

4 Comments

In my code, I tried doing value = 1000;, and it didn't update the value. I guess I needed to stop at the 2nd to last object, and then set the value. Why didn't value = 1000; work?
That's what this code does, with the last = steps.pop(). I just double-checked it, and it does work. Another test: a = {b:{c:1}}; d = a.b; d.c = 2; alert(a.b.c); // alerts 2, not 1
Weird. a = {b:{c:1}}; d = a.b.c; d = 2; alert(a.b.c); alerts 1. Wonder why that is. I guess because d was set to a value (int), not an object. That would make sense.
You missed a bit - I assigned d = a.b, and d.c = 2. That's why this works.
2

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 }

Comments

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

2 Comments

Shouldn't it be el[key[i]]?
According to @Kolink's answer, you need to stop at the 2nd to last property when setting, so that el is an object, and not an int. Also, when getting, you needed to use el[key[i]].
0

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

1 Comment

Needed an implementation that worked with arrays, this worked wonderfully. You're a lifesaver. :D

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.