1

Lets say that I have this object:

var obj = {
  level1 :{
    level2: {
      level3: {
        title: "winner"
      }
    }
  }
}

Now I want to update the title key using the next string (notice, I have a string, not actual variable) I have: let myString = "level1.level2.level3.title"; // note - myString value comes from $http method or something

Maybe something like this:

obj[myString] = "super-winner";

Unfortunately the above doesn't work.

In addition - sometimes I need to update an undefined object so I need something to make the object to be defined with a new empty object.

For example, If I have the next object:

var obj = {
  level1 : {}
  }
}

I still want to modify the obj with the level3.winner as above.

Reminder: obj[myString] = "super-winner";

How can I do that?

Raz Buchnik
  • 7,753
  • 14
  • 53
  • 96
  • You will need to write a function to do that which splits the string on `.`, iterates over the array of keys and navigates into the object optionally creating empty objecs. – Teemoh Oct 21 '18 at 05:56
  • there are libraries that support that https://lodash.com/docs/4.17.10#get – Slai Oct 21 '18 at 06:01
  • Possible duplicate of [Accessing nested JavaScript objects with string key](https://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key) – Slai Oct 21 '18 at 06:04

4 Answers4

1

This works

const obj = {
   // level1: {
    //     level2: {
    //         level3: {
    //             title: "winner"
    //         }
    //     }
    // }
}

const myString = "level1.level2.level3.title"; // note - myString value comes from $http method or something

const title = 'super-winner'

myString.split('.')
    .reduce(
        (acc, curr) => {
            if (acc[curr] === undefined && curr !== 'title') {
                acc[curr] = {}
            }

            if (curr === 'title') {
                acc[curr] = title
            }

            return acc[curr]
        }, obj
    );

console.log(obj) // {"level1":{"level2":{"level3":{"title":"super-winner"}}}}

This is zero-dependency solution, i.e. you don't have to use lodash or something bloating the size of your app.

Nurbol Alpysbayev
  • 19,522
  • 3
  • 54
  • 89
  • Is this possible making this work for `level1.level2[0].title` too? (notice the array inside object). BTW is reduce is ES6? – Raz Buchnik Oct 21 '18 at 06:40
  • Why not? Just add the appropriate condition e.g. `if (typeof acc[curr] === 'array') { acc[curr][0] = {title} }` Stackoverflow is not for solving everything for you, it is to *help* you to solve things. So you really should comprehend the code I wrote line-by-line, understand what [reduce method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) does and solve all the possible cases that are specific for your app (we cannot forsee all of them) – Nurbol Alpysbayev Oct 21 '18 at 07:00
1

Used "reduce" to achieve your desired result. Created a function "updateValue" where in you can pass obj - object to modify, str - property path to alter, value - value to be assigned at the property path

var obj1 = {
  level1 :{
    level2: {
      level3: {
        title: "winner"
      }
    }
  }
}

var obj2 = { level1: {} }

var obj3 = {
  level1 :{
    level2: {
      level3: {
        title: "winner"
      }
    }
  }
}

function updateValue(obj, str, value) {
let props = str.split('.'), arrIndex = -1
props.reduce((o,d,i) => ( 
                         arrIndex = d.indexOf('[') > -1 && d[d.indexOf('[') + 1],
                          arrIndex && (d = d.slice(0, d.indexOf('['))),
                           i == props.length - 1 
                              ? o[d] = value 
                              : (o[d] = o[d] || {}, (arrIndex && (Array.isArray(o[d]) || (o[d] = [o[d]]))), arrIndex && o[d][arrIndex] || o[d])
                        )
                , obj)
}

updateValue(obj1, 'level1.level2.level3.title', 'abcd')
updateValue(obj2, 'level1.level2.level3.title', 'abcd')
updateValue(obj3, 'level1.level2[0].title', 'abcd')

console.log(obj1)
console.log(obj2)
console.log(obj3)
Nitish Narang
  • 4,124
  • 2
  • 15
  • 22
  • Is this possible making this work for `level1.level2[0].title` too? (notice the array inside object). BTW is this ES6? – Raz Buchnik Oct 21 '18 at 06:37
  • Arrow functions ( => ) I used above was introduced in ES6. And regarding array indexes you mentioned, let me see to it. – Nitish Narang Oct 21 '18 at 06:40
  • As this array notation have many possibilities. I tried to cover whatever I could think of, you can have a look at it and see if this is what you are looking for. I have edited my answer above to cater this. Thanks. – Nitish Narang Oct 21 '18 at 07:04
  • Hey that does not work (it prints the first char of the string, not creating array instance inside the object – Raz Buchnik Oct 21 '18 at 08:08
  • Then I would suggest you open a new question with new requirement. Please don't forget to show your attempt, this helps fellow users to see what you are missing in your attempt. All the best :) – Nitish Narang Oct 21 '18 at 08:30
0

You can use .set function of lodash https://lodash.com/docs#set

ex: _.set(obj, 'level1.level2.level3.title', 'super-winner');

Or use ES6 syntax function:

var str = 'level1.level2.level3.title';
str.split('.').reduce((p, c, index) => {
    if (index === str.split('.').length - 1) {
        if (typeof p[c] !== "object") { // string, number, boolean, null, undefined
            p[c] = 'super-winner'
        }
        return p[c];
    } else {
        if (!p[c] || typeof p[c] !== 'object') {
            p[c] = {};
        }
        return p[c];
    }
}, obj)

console.log(obj);
hoangdv
  • 15,138
  • 4
  • 27
  • 48
0

This can be done by hand, indexing into the object structure repeatedly and creating new objects as necessary along the path to the destination key:

const updateField = (o, path, entry) => {
  path = path.split(".");
  let curr = o;
  
  while (path.length > 1) {
    const dir = path.shift();    
    const parent = curr;
    curr = curr[dir];

    if (undefined === curr) {
      parent[dir] = {};
      curr = parent[dir];
    }
  }
  
  if (path.length === 1) {
    curr[path.shift()] = entry;
  }
  
  return o;
};

var obj = {
  level1 : {
    level2: {
      level3: {
        title: "winner"
      }
    }
  }
};

console.log(JSON.stringify(updateField(obj, "level1.level2.level3.title", "super-winner"), null, 2));
console.log(JSON.stringify(updateField({}, "level1.level2.level3.title", "super-winner"), null, 2));
ggorlen
  • 44,755
  • 7
  • 76
  • 106