2

I am looking for a nice solution to access a property by string value, but if the property does not exist it should create it. If the root structure already has defined some parts of the structure, the attributes should not be overwritten, but merged instead.

For example if you have an empty object test and you want to set a deep structure without using eval. e.g.

test = {}
test.foo.name = "Hallo" // <<- foo is an Object
test.foo[3] = "Test" // <<- foo should remain as Object, not as Array 
test.foo.data[3].bar = 100 // <<- should not overwrite test.foo.name

I have written a solution that actually works, but it is quite bad code, I guess:

Also available as jsfiddle: https://jsfiddle.net/gvaLzqqf/4/

Object.setValue = function(node, flatKey, value) {
    flatKey = flatKey.replace("[", ".");
    flatKey = flatKey.replace("]", "");
    var parts = flatKey.split(".")
    var oldNode = node
    parts.forEach(function(key, index) {
      if (/^\+?(0|[1-9]\d*)$/.test(key)) {
        key = key * 1
        if (index > 0) {
          var oldValue = parts[index - 1]
          if (!Array.isArray(oldNode[oldValue])) {
            oldNode[oldValue] = []
            node = oldNode[oldValue]
          }
        }
      }
      if (node[key] == undefined) {
        node[key] = {}
      }
      oldNode = node
      node = node[key]
    }); // for each
    oldNode[parts[parts.length - 1]] = value
    return oldNode[parts[parts.length - 1]]
  } // function

var test = {}
Object.setValue(test, "foo.name", "Mr. Foo")
Object.setValue(test, "foo.data[0].bar", 100)
Object.setValue(test, "and.another[2].deep", 20)

console.log("test = " + JSON.stringify(test))
console.log("test.foo.data[0].bar = " + test.foo.data[0].bar)

How ever, is there any better way to achieve this?

0

2 Answers 2

4

You could split the path and reduce the path by walking the given object. If no Object exist, create a new property with the name, or an array. Later assign the value.

function setValue(object, path, value) {
    var way = path.replace(/\[/g, '.').replace(/\]/g, '').split('.'),
        last = way.pop();

    way.reduce(function (o, k, i, kk) {
        return o[k] = o[k] || (isFinite(i + 1 in kk ? kk[i + 1] : last) ? [] : {});
    }, object)[last] = value;
}

var test = {};
setValue(test, "foo.name", "Mr. Foo");
setValue(test, "foo.data[0].bar", 100);
setValue(test, "and.another[2].deep", 20);
console.log(test);

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

3 Comments

Exaclty what I was searching for! Thanks! This solution is as elegant as it is beautiful. However one thing is still missing, Arrays will be handled as Objects. If you run your solution you will see that arrays are used as string-keys instead of integer keys. Hence the structure is wrong at the end: instead of "key" : [ {}, {},... ] you have "key" : {"0": {}, "1":{}... ]
i added the wanted feature for array, but you get now "another": [ undefined, undefined, { "deep": 20 }].
Wow! Of course you will have undefined, since it is an array. But that is exactly what one would expect. Thank you very much, very good, nice and light weight solution.
1

I wouldn't reinvent the wheel in this case, and instead use lodash. Specifically the set() function. As per their example:

var object = { 'a': [{ 'b': { 'c': 3 } }] };

_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// => 4

_.set(object, ['x', '0', 'y', 'z'], 5);
console.log(object.x[0].y.z);
// => 5

3 Comments

This solution is working though it is not answering the question. I decided to accept the solution anyway, lodash is not to heavy to include it in my project. So thanks for the solution. How ever the solution of Nina Scholz is exactly what I was searching for.
Your question explicitly said, I am looking for a nice solution to access a property by string value & How ever, is there any better way to achieve this?. I would argue that I have exactly answered your question, and in fact is the correct answer. Using lodash is a better way to solve your problem, as it does exactly what you need already, and will not cause you bugs etc. Your question did not specify not to use already proven libraries.
I am sorry that I can only mark one solution as "accepted". Ninas solution is way more what I was looking for, it is as nice as yours from the usability point, but it does not require to add a complete third party library. I have to admit that my question was not clear enough, and that I should have added that I prefer a non third party solution, if there is an elegant way to implement this. However, Ninas solution solves exactly my problem and does not do more. That is why I prefer to give her the kudos.

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.