Set object value at the string path

Given an object, a path in the string or array of strings format, and a value, update the value at the given path in the object.

This is a polyfill for lodash._set() method and is opposite of lodash._get() method.

Example

const 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

To implement this function, we will first check if the provided path is a string or an array of strings.

If it is string then filter all the special characters like [, ] and split the string on . to get all the path keys in an array.

Then using a helper function we can assign the value to the provided path.

  • Get only the first key from the path array and aggregate the rest of the keys.
  • If there are no more keys left to update, assign the value to the current key.
  • Else recursively call the same function with the current value for the next path.
  • While moving to the next path, check the type of key, if it is numeric the value should be an array thus pass array, else if it is a string pass the object.

Note:- This will override the existing value and assign a new one.

const helper = (obj, path, value) => {
    // get the current and the remaining keys from the path
    let [current, ...rest] =  path;
  
    // if there are more keys
    // add the value as an object or array
    // depending upon the typeof key
    if(rest.length > 0){
        // if there is no key present
        // create a new one
        if(!obj[current]){
          // if the key is numeric
          // add an array
          // else add an object
          const isNumber = `${+rest[0]}` === rest[0];
          obj[current] = isNumber ? [] : {};
        }
            
        // recurisvely update the remaining path
        // if the last path is not of object type
        // but key is then
        // create an object or array based on the key
        // and update the value
        if(typeof obj[current] !== 'object'){
          // determine if the key is string or numeric 
          const isNumber = `${+rest[0]}` === rest[0];
          obj[current] = helper(isNumber ? [] : {}, rest, value)
        }
        // else directly update value
        else{
          obj[current] = helper(obj[current], rest, value);
        }
    }
    // else directly assing the value to the key
    else{
      obj[current] = value;
    }
  
    // return the updated obj
    return obj;
 }

const set = (obj, path, value) => {
   let pathArr = path;
  
   // if path is of string type
   // replace the special characters
   // and split the string on . to get the path keys array
   if(typeof path === 'string'){
     pathArr = path.replace('[', '.').replace(']', '').split(".");
   }
   
   // use the helper function to update
   helper(obj, pathArr, value);
};
Input:
const abc = {
  a: {
    b: {
      c: [1, 2, 3]
    },
    d: {
      a: "hello"
    }
  }
};

const instance1 = JSON.parse(JSON.stringify(abc));
set(instance1, 'a.b.c', 'learnersbucket');
console.log(instance1.a.b.c);

const instance2 = JSON.parse(JSON.stringify(abc));
set(instance2, 'a.b.c.0', 'learnersbucket');
console.log(instance2.a.b.c[0]);

const instance3 = JSON.parse(JSON.stringify(abc));
set(instance3, 'a.b.c[1]', 'learnersbucket');
console.log(instance3.a.b.c[1]);

const instance4 = JSON.parse(JSON.stringify(abc));
set(instance4, ['a', 'b', 'c', '2'], 'learnersbucket');
console.log(instance4.a.b.c[2]);

const instance5 = JSON.parse(JSON.stringify(abc));
set(instance5, 'a.b.c[3]', 'learnersbucket');
console.log(instance5.a.b.c[3])

const instance6 = JSON.parse(JSON.stringify(abc));
set(instance6, 'a.c.d[0]', 'learnersbucket');
// valid digits treated as array elements
console.log(instance6.a.c.d[0]);

const instance7 = JSON.parse(JSON.stringify(abc));
set(instance7, 'a.d.01', 'learnersbucket');
// invalid digits treated as property string
console.log(instance7.a.d['01']);

const object = { 'a': [{ 'b': { 'c': 3 } }] };
set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);

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

Output:
"learnersbucket"
"learnersbucket"
"learnersbucket"
"learnersbucket"
"learnersbucket"
"learnersbucket"
"learnersbucket"
4
5