147

I have an object that could be any number of levels deep and could have any existing properties. For example:

var obj = {
    db: {
        mongodb: {
            host: 'localhost'
        }
    }
};

On that I would like to set (or overwrite) properties like so:

set('db.mongodb.user', 'root');
// or:
set('foo.bar', 'baz');

Where the property string can have any depth, and the value can be any type/thing.
Objects and arrays as values don't need to be merged, should the property key already exist.

Previous example would produce following object:

var obj = {
    db: {
        mongodb: {
            host: 'localhost',
            user: 'root'
        }
    },
    foo: {
        bar: baz
    }
};

How can I realize such a function?

John B.
  • 2,309
  • 5
  • 23
  • 22
  • What should be the result for `set('foo', 'bar'); set('foo.baz', 'qux');`, where `foo` first holds a `String` then becomes an `Object`? What happens to `'bar'`? – Jonathan Lonowski Sep 21 '13 at 19:52
  • 2
    possible duplicate of [Accessing nested JavaScript objects with string key](http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key) – Robert Levy Sep 21 '13 at 19:53
  • could posible this help: http://stackoverflow.com/questions/695050/how-do-i-add-a-property-to-a-javascript-object-using-a-variable-as-the-name – Rene M. Sep 21 '13 at 19:56
  • 1
    If you remove the `set()` method and just do `obj.db.mongodb.user = 'root';` you'd have exactly what you seem to be wanting ? – adeneo Sep 21 '13 at 19:57
  • @JonathanLonowski Lonowski `bar` gets overwritten by the `Object`. @adeneo and @rmertins Indeed :) But I have to wrap some other logic around unfortunately. @Robert Levy I found that one and got the accessing working, but setting it seems so much more complicated... – John B. Sep 21 '13 at 20:13
  • 1
    Possible duplicate of [Javascript: how to dynamically create nested objects using object names given by an array](http://stackoverflow.com/questions/5484673/javascript-how-to-dynamically-create-nested-objects-using-object-names-given-by) – Cody Moniz Apr 21 '16 at 19:13

28 Answers28

145

This function, using the arguments you specified, should add/update the data in the obj container. Note that you need to keep track of which elements in obj schema are containers and which are values (strings, ints, etc.) otherwise you will start throwing exceptions.

obj = {};  // global object

function set(path, value) {
    var schema = obj;  // a moving reference to internal objects within obj
    var pList = path.split('.');
    var len = pList.length;
    for(var i = 0; i < len-1; i++) {
        var elem = pList[i];
        if( !schema[elem] ) schema[elem] = {}
        schema = schema[elem];
    }

    schema[pList[len-1]] = value;
}

set('mongo.db.user', 'root');
bpmason1
  • 1,900
  • 1
  • 12
  • 9
  • 2
    @bpmason1 could you explain why you used `var schema = obj` instead of just `obj` everywhere? – sman591 Jun 11 '15 at 16:37
  • 4
    @sman591 `schema` is a pointer which gets moved down the path with `schema = schema[elem]`. So after the for loop, `schema[pList[len - 1]]` points to mongo.db.user in `obj`. – webjay Oct 18 '17 at 19:43
  • that solved my problem thanks, couldn't find this in MDN docs. But i have another doubt, if assignment operator gives a reference to the internal objects then how to make a separate object2 from the object1 so that changes done on object2 will not reflect on the object1. – Onix Feb 18 '19 at 17:24
  • 1
    @Onix You can make use of lodash `cloneDeep` function for this. – Aakash Thakur May 15 '20 at 13:22
  • @Onix const clone = JSON.parse(JSON.stringify(obj)) – Mike Makuch Jun 24 '20 at 16:43
  • @Onix that will never happen. only when you change props that happens – Siddharth Shyniben Jul 31 '21 at 10:10
  • how can we add an array value as a nested element? @bpmason1 – Phạm Ân Jan 13 '23 at 07:43
128

Lodash has a _.set() method.

_.set(obj, 'db.mongodb.user', 'root');
_.set(obj, 'foo.bar', 'baz');
aheuermann
  • 2,599
  • 2
  • 20
  • 17
  • 2
    Can it be used to set the value for key as well? if yes can you share an example. Thank you – sage poudel Jul 22 '19 at 01:25
  • This is great, but how would you keep track of / determine the path ? – Tom Oct 09 '19 at 07:46
  • @aheuermann I've several levels of nested array how can I set the property in case of multilevel nested array of objects – Aminul May 29 '21 at 05:00
  • lodash set also accepts an array for the path, e.g. `_.set(obj, ['db', 'mongodb', 'user'], 'root');` – congusbongus Jul 22 '21 at 07:07
  • 4
    Be aware that this will not work as expected when part of key contains number like 'foo.bar.350350'. It will instead create 350350 empty elements! – daGrevis Dec 08 '21 at 15:02
  • how do you make lodash not create an array but string always even when it's numbers? – Joseph Astrahan Jun 18 '22 at 23:56
  • Therefore you need to install lodash in your project. I would prefer a vanilla JS solution like the one from bpmason1 – leonp5 Aug 09 '22 at 12:45
31

I just write a small function using ES6 + recursion to achieve the goal.

updateObjProp = (obj, value, propPath) => {
    const [head, ...rest] = propPath.split('.');

    !rest.length
        ? obj[head] = value
        : this.updateObjProp(obj[head], value, rest.join('.'));
}

const user = {profile: {name: 'foo'}};
updateObjProp(user, 'fooChanged', 'profile.name');

I used it a lot on react to update state, it worked pretty well for me.

Nacho Coloma
  • 7,070
  • 2
  • 40
  • 43
Bruno Joaquim
  • 1,423
  • 1
  • 14
  • 15
  • 2
    this was handy, i had to put a toString() on proPath to make it work with nested properties, but after that it worked great. const [head, ...rest] = propPath.toString().split('.'); – WizardsOfWor Aug 21 '19 at 20:15
  • 2
    @user738048 @Bruno-Joaquim the line `this.updateStateProp(obj[head], value, rest);` should be `this.updateStateProp(obj[head], value, rest.join());` – ma.mehralian Nov 23 '19 at 07:21
25

A bit late but here's a non-library, simpler answer:

/**
 * Dynamically sets a deeply nested value in an object.
 * Optionally "bores" a path to it if its undefined.
 * @function
 * @param {!object} obj  - The object which contains the value you want to change/set.
 * @param {!array} path  - The array representation of path to the value you want to change/set.
 * @param {!mixed} value - The value you want to set it to.
 * @param {boolean} setrecursively - If true, will set value of non-existing path as well.
 */
function setDeep(obj, path, value, setrecursively = false) {
    path.reduce((a, b, level) => {
        if (setrecursively && typeof a[b] === "undefined" && level !== path.length){
            a[b] = {};
            return a[b];
        }

        if (level === path.length){
            a[b] = value;
            return value;
        } 
        return a[b];
    }, obj);
}

This function I made can do exactly what you need and a little more.

lets say we want to change the target value that is deeply nested in this object:

let myObj = {
    level1: {
        level2: {
           target: 1
       }
    }
}

So we would call our function like so:

setDeep(myObj, ["level1", "level2", "target1"], 3);

will result in:

myObj = { level1: { level2: { target: 3 } } }

Setting the set recursively flag to true will set objects if they don't exist.

setDeep(myObj, ["new", "path", "target"], 3, true);

will result in this:

obj = myObj = {
    new: {
         path: {
             target: 3
         }
    },
    level1: {
        level2: {
           target: 3
       }
    }
}
Philll_t
  • 4,267
  • 5
  • 45
  • 59
  • 1
    Used this code, clean and simple. Instead of computing `level` I used `reduce`'s third argument. – Juan Lanus Apr 09 '19 at 22:45
  • 6
    I believe that `level` needs to be +1 or `path.length` -1 – ThomasReggi Oct 29 '20 at 02:04
  • 2
    you shouldn't use reduce when not performing a reduction. – McTrafik May 20 '21 at 18:08
  • @McTrafik what should you use instead – Philll_t May 20 '21 at 18:09
  • 1
    A loop. The reduce function is just sugar syntax for a for-loop with an accumulator applicable for a reduction. See something like this: https://medium.com/winnintech/implementing-reduce-in-javascript-part-1-7ea8711e194 and this code DOES NOT accumulate anything nor does it perform a reduction so the reduce call here is a misuse of the pattern. – McTrafik May 21 '21 at 23:40
  • but the value does accumulate as it traverses through deeply nested values. I don't think using reduce is a bad idea. Besides, this code also sets the non-existing values. I wouldn't worry too much about it. – Philll_t Jul 20 '21 at 19:40
  • 1
    `reduce` should not be used with side effects (intended as a pure function). A good rule of thumb is that if the return of `map`, `filter`, or `reduce` is not used, the function itself should be replaced with `forEach`. – Kendall Jan 20 '22 at 22:39
23

We can use a recursion function:

/**
 * Sets a value of nested key string descriptor inside a Object.
 * It changes the passed object.
 * Ex:
 *    let obj = {a: {b:{c:'initial'}}}
 *    setNestedKey(obj, ['a', 'b', 'c'], 'changed-value')
 *    assert(obj === {a: {b:{c:'changed-value'}}})
 *
 * @param {[Object]} obj   Object to set the nested key
 * @param {[Array]} path  An array to describe the path(Ex: ['a', 'b', 'c'])
 * @param {[Object]} value Any value
 */
export const setNestedKey = (obj, path, value) => {
  if (path.length === 1) {
    obj[path] = value
    return
  }
  return setNestedKey(obj[path[0]], path.slice(1), value)
}

It's more simple!

chim
  • 8,407
  • 3
  • 52
  • 60
Hemã Vidal
  • 1,742
  • 2
  • 17
  • 28
12

Inspired by @bpmason1's answer:

function leaf(obj, path, value) {
  const pList = path.split('.');
  const key = pList.pop();
  const pointer = pList.reduce((accumulator, currentValue) => {
    if (accumulator[currentValue] === undefined) accumulator[currentValue] = {};
    return accumulator[currentValue];
  }, obj);
  pointer[key] = value;
  return obj;
}

const obj = {
  boats: {
    m1: 'lady blue'
  }
};
leaf(obj, 'boats.m1', 'lady blue II');
leaf(obj, 'boats.m2', 'lady bird');
console.log(obj); // { boats: { m1: 'lady blue II', m2: 'lady bird' } }
Moritz Ringler
  • 9,772
  • 9
  • 21
  • 34
webjay
  • 5,358
  • 9
  • 45
  • 62
10

ES6 has a pretty cool way to do this too using Computed Property Name and Rest Parameter.

const obj = {
  levelOne: {
    levelTwo: {
      levelThree: "Set this one!"
    }
  }
}

const updatedObj = {
  ...obj,
  levelOne: {
    ...obj.levelOne,
    levelTwo: {
      ...obj.levelOne.levelTwo,
      levelThree: "I am now updated!"
    }
  }
}

If levelThree is a dynamic property i.e. to set any of the property in levelTwo, you can use [propertyName]: "I am now updated!" where propertyName holds the name of the property in levelTwo.

ron4ex
  • 1,073
  • 10
  • 21
10

I came up with my own solution using pure es6 and recursion that doesn't mutate the original object.

const setNestedProp = (obj = {}, [first, ...rest] , value) => ({
  ...obj,
  [first]: rest.length
    ? setNestedProp(obj[first], rest, value)
    : value
});

const result = setNestedProp({}, ["first", "second", "a"], 
"foo");
const result2 = setNestedProp(result, ["first", "second", "b"], "bar");

console.log(result);
console.log(result2);
Henry Ing-Simmons
  • 1,362
  • 5
  • 18
  • 25
9

Lodash has a method called update that does exactly what you need.

This method receives the following parameters:

  1. The object to update
  2. The path of the property to update (the property can be deeply nested)
  3. A function that returns the value to update (given the original value as a parameter)

In your example it would look like this:

_.update(obj, 'db.mongodb.user', function(originalValue) {
  return 'root'
})
brafdlog
  • 2,642
  • 1
  • 18
  • 21
3

I needed to achieve the same thing, but in Node.js... So, I found this nice module: https://www.npmjs.com/package/nested-property

Example:

var mod = require("nested-property");
var obj = {
  a: {
    b: {
      c: {
        d: 5
      }
    }
  }
};
console.log(mod.get(obj, "a.b.c.d"));
mod.set(obj, "a.b.c.d", 6);
console.log(mod.get(obj, "a.b.c.d"));
Rehmat
  • 2,121
  • 2
  • 24
  • 28
  • how to solve for complex nested objects. ``` const x = { 'one': 1, 'two': 2, 'three': { 'one': 1, 'two': 2, 'three': [ { 'one': 1 }, { 'one': 'ONE' }, { 'one': 'I' } ] }, 'four': [0, 1, 2] }; console.log(np.get(x, 'three.three[0].one')); ``` – Sumukha H S Jan 25 '20 at 03:05
2

I created gist for setting and getting obj values by string based on correct answer. You can download it or use it as npm/yarn package.

// yarn add gist:5ceba1081bbf0162b98860b34a511a92
// npm install gist:5ceba1081bbf0162b98860b34a511a92
export const DeepObject = {
  set: setDeep,
  get: getDeep
};

// https://stackoverflow.com/a/6491621
function getDeep(obj: Object, path: string) {
  path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
  path = path.replace(/^\./, '');           // strip a leading dot
  const a = path.split('.');
  for (let i = 0, l = a.length; i < l; ++i) {
    const n = a[i];
    if (n in obj) {
      obj = obj[n];
    } else {
      return;
    }
  }

  return obj;
}

// https://stackoverflow.com/a/18937118
function setDeep(obj: Object, path: string, value: any) {
  let schema = obj;  // a moving reference to internal objects within obj
  const pList = path.split('.');
  const len = pList.length;
  for (let i = 0; i < len - 1; i++) {
    const elem = pList[i];
    if (!schema[elem]) {
      schema[elem] = {};
    }
    schema = schema[elem];
  }

  schema[pList[len - 1]] = value;
}

// Usage
// import {DeepObject} from 'somePath'
//
// const obj = {
//   a: 4,
//   b: {
//     c: {
//       d: 2
//     }
//   }
// };
//
// DeepObject.set(obj, 'b.c.d', 10); // sets obj.b.c.d to 10
// console.log(DeepObject.get(obj, 'b.c.d')); // returns 10
Chiffie
  • 581
  • 3
  • 18
2

Extending the accepted answer provided by @bpmason1, to support arrays in string path e.g. string path can be 'db.mongodb.users[0].name' and 'db.mongodb.users[1].name'.

It will set the property value, which if doesn't exist, will be created.

var obj = {};

function set(path, value) {
  var schema = obj;
  var keysList = path.split('.');
  var len = keysList.length;
  for (var i = 0; i < len - 1; i++) {
    var key = keysList[i];
    // checking if key represents an array element e.g. users[0]
    if (key.includes('[')) {
      //getting propertyName 'users' form key 'users[0]'
      var propertyName = key.substr(0, key.length - key.substr(key.indexOf("["), key.length - key.indexOf("[")).length);
      if (!schema[propertyName]) {
        schema[propertyName] = [];
      }
      // schema['users'][getting index 0 from 'users[0]']
      if (!schema[propertyName][parseInt(key.substr(key.indexOf("[") + 1, key.indexOf("]") - key.indexOf("[") - 1))]) {
        // if it doesn't exist create and initialise it
        schema = schema[propertyName][parseInt(key.substr(key.indexOf("[") + 1, key.indexOf("]") - key.indexOf("[") - 1))] = {};
      } else {
        schema = schema[propertyName][parseInt(key.substr(key.indexOf("[") + 1, key.indexOf("]") - key.indexOf("[") - 1))];
      }
      continue;
    }
    if (!schema[key]) {
      schema[key] = {};
    }
    schema = schema[key];
  } //loop ends
  // if last key is array element
  if (keysList[len - 1].includes('[')) {
    //getting propertyName 'users' form key 'users[0]'
    var propertyName = keysList[len - 1].substr(0, keysList[len - 1].length - keysList[len - 1].substr(keysList[len - 1].indexOf("["), keysList[len - 1].length - keysList[len - 1].indexOf("[")).length);
    if (!schema[propertyName]) {
      schema[propertyName] = [];
    }
    // schema[users][0] = value;
    schema[propertyName][parseInt(keysList[len - 1].substr(keysList[len - 1].indexOf("[") + 1, keysList[len - 1].indexOf("]") - keysList[len - 1].indexOf("[") - 1))] = value;
  } else {
    schema[keysList[len - 1]] = value;
  }
}

// will create if not exist
set("mongo.db.users[0].name.firstname", "hii0");
set("mongo.db.users[1].name.firstname", "hii1");
set("mongo.db.users[2].name", {
  "firstname": "hii2"
});
set("mongo.db.other", "xx");
console.log(obj);

// will set if exist
set("mongo.db.other", "yy");
console.log(obj);
Ashish Dahiya
  • 650
  • 5
  • 14
2

Here's a solution using ES 12

function set(obj = {}, key, val) {
  const keys = key.split('.')
  const last = keys.pop()
  keys.reduce((o, k) => o[k] ??= {}, obj)[last] = val
}

(For older versions of javascript, you can do do o[k] || o[k] = {} in the reduce instead)

First, we set keys to be an array of everything but the last key.

Then in the reduce, the accumulator goes one level deeper into obj each time, initializing it to an empty object if it the value at that key is not defined.

Finally, we set the value at the last key to val.

1

If you only need to change deeper nested objects, then another method could be to reference the object. As JS objects are handled by their references, you can create a reference to an object you have string-key access to.

Example:

// The object we want to modify:
var obj = {
    db: {
        mongodb: {
            host: 'localhost',
            user: 'root'
        }
    },
    foo: {
        bar: baz
    }
};

var key1 = 'mongodb';
var key2 = 'host';

var myRef = obj.db[key1]; //this creates a reference to obj.db['mongodb']

myRef[key2] = 'my new string';

// The object now looks like:
var obj = {
    db: {
        mongodb: {
            host: 'my new string',
            user: 'root'
        }
    },
    foo: {
        bar: baz
    }
};
aggregate1166877
  • 2,196
  • 23
  • 38
1

Another approach is to use recursion to dig through the object:

(function(root){

  function NestedSetterAndGetter(){
    function setValueByArray(obj, parts, value){

      if(!parts){
        throw 'No parts array passed in';
      }

      if(parts.length === 0){
        throw 'parts should never have a length of 0';
      }

      if(parts.length === 1){
        obj[parts[0]] = value;
      } else {
        var next = parts.shift();

        if(!obj[next]){
          obj[next] = {};
        }
        setValueByArray(obj[next], parts, value);
      }
    }

    function getValueByArray(obj, parts, value){

      if(!parts) {
        return null;
      }

      if(parts.length === 1){
        return obj[parts[0]];
      } else {
        var next = parts.shift();

        if(!obj[next]){
          return null;
        }
        return getValueByArray(obj[next], parts, value);
      }
    }

    this.set = function(obj, path, value) {
      setValueByArray(obj, path.split('.'), value);
    };

    this.get = function(obj, path){
      return getValueByArray(obj, path.split('.'));
    };

  }
  root.NestedSetterAndGetter = NestedSetterAndGetter;

})(this);

var setter = new this.NestedSetterAndGetter();

var o = {};
setter.set(o, 'a.b.c', 'apple');
console.log(o); //=> { a: { b: { c: 'apple'}}}

var z = { a: { b: { c: { d: 'test' } } } };
setter.set(z, 'a.b.c', {dd: 'zzz'}); 

console.log(JSON.stringify(z)); //=> {"a":{"b":{"c":{"dd":"zzz"}}}}
console.log(JSON.stringify(setter.get(z, 'a.b.c'))); //=> {"dd":"zzz"}
console.log(JSON.stringify(setter.get(z, 'a.b'))); //=> {"c":{"dd":"zzz"}}
ed.
  • 2,696
  • 3
  • 22
  • 25
1

Late to the party - here's a vanilla js function that accepts a path as an argument and returns the modified object/json

let orig_json = {
  string: "Hi",
  number: 0,
  boolean: false,
  object: {
    subString: "Hello",
    subNumber: 1,
    subBoolean: true,
    subObject: {
      subSubString: "Hello World"
    },
    subArray: ["-1", "-2", "-3"]
  },
  array: ["1", "2", "3"]
}

function changeValue(obj_path, value, json) {
  let keys = obj_path.split(".")
  let obj = { ...json },
    tmpobj = {},
    prevobj = {}
  for (let x = keys.length - 1; x >= 0; x--) {
    if (x == 0) {
      obj[keys[0]] = tmpobj
    } else {
      let toeval = 'json.' + keys.slice(0, x).join('.');
      prevobj = { ...tmpobj
      }
      tmpobj = eval(toeval);
      if (x == keys.length - 1) tmpobj[keys[x]] = value
      else {
        tmpobj[keys[x]] = prevobj
      }
    }
  }
  return obj
}

let newjson = changeValue("object.subObject.subSubString", "Goodbye world", orig_json);
console.log(newjson)
Kinglish
  • 23,358
  • 3
  • 22
  • 43
1

Another solution to add or override properties:

function propertySetter(property, value) {
  const sampleObject = {
    string: "Hi",
    number: 0,
    boolean: false,
    object: {
      subString: "Hello",
      subNumber: 1,
      subBoolean: true,
      subObject: {
        subSubString: "Hello World",
      },
      subArray: ["-1", "-2", "-3"],
    },
    array: ["1", "2", "3"],
  };

  const keys = property.split(".");
  const propertyName = keys.pop();
  let propertyParent = sampleObject;
  while (keys.length > 0) {
    const key = keys.shift();
    if (!(key in propertyParent)) {
      propertyParent[key] = {};
    }
    propertyParent = propertyParent[key];
  }
  propertyParent[propertyName] = value;
  return sampleObject;
}

console.log(propertySetter("object.subObject.anotherSubString", "Hello you"));

console.log(propertySetter("object.subObject.subSubString", "Hello Earth"));

console.log(propertySetter("object.subObject.nextSubString.subSubSubString", "Helloooo"));
Mario Varchmin
  • 3,704
  • 4
  • 18
  • 33
1

Inspired by ImmutableJS setIn method which will never mutate the original. This works with mixed array and object nested values.

function setIn(obj = {}, [prop, ...rest], value) {
    const newObj = Array.isArray(obj) ? [...obj] : {...obj};
    newObj[prop] = rest.length ? setIn(obj[prop], rest, value) : value;
    return newObj;
}

var obj = {
  a: {
    b: {
      c: [
        {d: 5}
      ]
    }
  }
};

const newObj = setIn(obj, ["a", "b", "c", 0, "x"], "new");

//obj === {a: {b: {c: [{d: 5}]}}}
//newObj === {a: {b: {c: [{d: 5, x: "new"}]}}}
Jeff Walters
  • 4,403
  • 2
  • 16
  • 12
1

As @aheuermann sed, you can use set from lodash library,

However, if you don't want to add lodash to your project for some reason you can use a recursion function that sets/overrides a value in an object.

/**
 * recursion function that called in main function 
 * @param obj initial JSON
 * @param keysList array of keys
 * @param value value that you want to set
 * @returns final JSON
 */
function recursionSet(obj, keysList, value) {
    const key = keysList[0]
    if (keysList.length === 1) return { ...obj, [key]: value }
    return { ...obj, [key]: (recursionSet(obj?.[key] || {}, keysList.slice(1), value)) }
}

/**
 * main function that you can call for set a value in an object by nested keys
 * @param obj initial JSON
 * @param keysString nested keys that seprated by "."
 * @param value value that you want to set
 * @returns final JSON
 */
function objectSet(obj, keysString, value) {
    return recursionSet(obj, keysString.split('.'), value)
}

// simple usage
const a1 = {}
console.log('simple usage:', objectSet(a1, "b.c.d", 5))

// keep the initial data
const a2 = {b:{e: 8}}
console.log('keep the initial data:', objectSet(a2, "b.c.d", 5))

// override data
const a3 = {b:{e: 8, c:2}}
console.log('override data:', objectSet(a3, "b.c.d", 5))

// complex value
const a4 = {b:{e: 8, c:2}}
console.log('complex value:', objectSet(a4, "b.c.d", {f:12}))
Masoud Aghaei
  • 775
  • 7
  • 20
0

If you would like a function that required prior properties to exist, then you could use something like this, it would also return a flag stating whether it managed to find and set the nested property.

function set(obj, path, value) {
    var parts = (path || '').split('.');
    // using 'every' so we can return a flag stating whether we managed to set the value.
    return parts.every((p, i) => {
        if (!obj) return false; // cancel early as we havent found a nested prop.
        if (i === parts.length - 1){ // we're at the final part of the path.
            obj[parts[i]] = value;          
        }else{
            obj = obj[parts[i]]; // overwrite the functions reference of the object with the nested one.            
        }   
        return true;        
    });
}
C Smith
  • 222
  • 1
  • 6
0

JQuery has an extend method:

https://api.jquery.com/jquery.extend/

just pass the overwrites as an object and it will merge the two.

Yamcha
  • 1,264
  • 18
  • 24
0

Inspired by ClojureScript's assoc-in (https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/core.cljs#L5280), using recursion:

/**
 * Associate value (v) in object/array (m) at key/index (k).
 * If m is falsy, use new object.
 * Returns the updated object/array.
 */
function assoc(m, k, v) {
    m = (m || {});
    m[k] = v;
    return m;
}

/**
 * Associate value (v) in nested object/array (m) using sequence of keys (ks)
 * to identify the path to the nested key/index.
 * If one of the values in the nested object/array doesn't exist, it adds
 * a new object.
 */
function assoc_in(m={}, [k, ...ks], v) {
    return ks.length ? assoc(m, k, assoc_in(m[k], ks, v)) : assoc(m, k, v);
}

/**
 * Associate value (v) in nested object/array (m) using key string notation (s)
 * (e.g. "k1.k2").
 */
function set(m, s, v) {
    ks = s.split(".");
    return assoc_in(m, ks, v);
}

Note:

With the provided implementation,

assoc_in({"a": 1}, ["a", "b"], 2) 

returns

{"a": 1}

I would prefer that it throw an error in this case. If desired, you can add a check in assoc to verify m is either an object or array and throw an error otherwise.

0

I tried to write this set method in short, it may help someone!

function set(obj, key, value) {
 let keys = key.split('.');
 if(keys.length<2){ obj[key] = value; return obj; }

 let lastKey = keys.pop();

 let fun = `obj.${keys.join('.')} = {${lastKey}: '${value}'};`;
 return new Function(fun)();
}

var obj = {
"hello": {
    "world": "test"
}
};

set(obj, "hello.world", 'test updated'); 
console.log(obj);

set(obj, "hello.world.again", 'hello again'); 
console.log(obj);

set(obj, "hello.world.again.onece_again", 'hello once again');
console.log(obj);
Arun Saini
  • 6,714
  • 1
  • 19
  • 22
0
const set = (o, path, value) => {
    const props = path.split('.');
    const prop = props.shift()
    if (props.length === 0) {
        o[prop] = value
    } else {
        o[prop] = o[prop] ?? {}
        set(o[prop], props.join('.'), value)
    }
}
Mario Varchmin
  • 3,704
  • 4
  • 18
  • 33
agelbess
  • 4,249
  • 3
  • 20
  • 21
0

in case you want to deeply update or insert an object try this :-

 let init = {
       abc: {
           c: {1: 2, 3: 5, 0: {l: 3}},
           d: 100
       }
    }
    Object.prototype.deepUpdate = function(update){
       let key = Object.keys(update);
       key.forEach((k) => {
           if(typeof update[key] == "object"){
              this[k].deepUpdate(update[key], this[k])
           }
           else 
           this[k] = update[k]
       })
    }

    init.deepUpdate({abc: {c: {l: 10}}})
    console.log(init)

but make sure it will change the original object, you can make it to not change the original object :

JSON.parse(JSON.stringify(init)).deepUpdate({abc: {c: {l: 10}}})
0

Improving on bpmason1's answer: -adds a get() function. -It does not require to define global storage object -It is accessible from same domain iFrames

function set(path, value) 
{
  var schema = parent.document;
  path="data."+path;
  var pList = path.split('.');
  var len = pList.length;
  for(var i = 0; i < len-1; i++) 
  {
    if(!schema[pList[i]]) 
      schema[pList[i]] = {}
    schema = schema[pList[i]];
  }
  schema[pList[len-1]] = value;
}

function get(path) 
{
  path="data."+path;
  var schema=parent.document;
  var pList = path.split('.');
  for(var i = 0; i < pList.length; i++) 
    schema = schema[pList[i]];
  return schema;
}

set('mongo.db.user', 'root');
set('mongo.db.name', 'glen');

console.log(get('mongo.db.name'));  //prints 'glen'
Gleno
  • 85
  • 4
0

Sometimes if the key also has dots (.) it its string this may pose a problem. As even that single key will now get split into various keys.

It is best to store the key path in an array, like so: ['db','mongodb','user'] and assign the value dynamically with the below function.

function set(obj, path, value) {
  var schema = obj;
  var pList = path.slice();
  var len = pList.length;
  for (var i = 0; i < len - 1; i++) {
    var elem = pList[i];
    if (!schema[elem]) schema[elem] = {};
    schema = schema[elem];
  }
  schema[pList[len - 1]] = value;
}
    
let path = ['db','mongodb','user'];
set(obj, path, 'root');
0

I want to leave my answer for this interesting topic. Creating a function that sets dynamic properties for an object can be difficult.

const entity = {
  haveDogs: true,
  dogs: ['Maya', 'Perla']
}

function isObject(obj) {
  return obj instanceof Object && obj.constructor === Object;
}

function setSchema(key, schema, value) {
  if (!isObject(value)) {
    schema[key] = value;
    return
  }
      
  if (!schema[key]) schema[key] = {}
  schema[key] = mutate(schema[key], value);
}

function mutate(obj, newObjData) {
    const keys = Object.keys(newObjData)
    
    for (const key of keys) {
      let schema = obj
      const list = key.split('.')
      const value = newObjData[key]
      const total = list.length - 1
      
      if (list.length === 1) {
        setSchema(key, schema, value)
        continue
      }
      
      for (let i = 0; i < total; i++) {
        const elem = list[i];
        if (!schema[elem]) schema[elem] = {}
        schema = schema[elem]
      }
      
      const subField = list[total]
      setSchema(subField, schema, value)
    }

    return obj
}

mutate(entity, {
  haveDogs: false,
  'pet1.pet2.pet3.pet4.pet5': 'pets',
  'bestFriends.list': ['Maya', 'Lucas'],
  friends: {
    'whitelist.permitted': ['Maya', 'Perla'], 
    'party.blocked': ['Juan', 'Trump']
  }
})

console.log('[entity]', entity)
Husdady
  • 145
  • 3
  • 12