Javascript: how to dynamically create nested objects using object names given by an array
Asked Answered
C

26

88

I hope someone can help me with this Javascript.

I have an Object called "Settings" and I would like to write a function that adds new settings to that object.

The new setting's name and value are provided as strings. The string giving the setting's name is then split by the underscores into an array. The new setting should get added to the existing "Settings" object by creating new nested objects with the names given by each part of the array, except the last part which should be a string giving the setting's value. I should then be able to refer to the setting and e.g. alert its value. I can do this in a static way like this...

var Settings = {};
var newSettingName = "Modules_Video_Plugin";
var newSettingValue = "JWPlayer";
var newSettingNameArray = newSettingName.split("_");

Settings[newSettingNameArray[0]] = {};
Settings[newSettingNameArray[0]][newSettingNameArray[1]] = {};
Settings[newSettingNameArray[0]][newSettingNameArray[1]][newSettingNameArray[2]] = newSettingValue;

alert(Settings.Modules.Mediaplayers.Video.Plugin);

... the part that creates the nested objects is doing this ...

Settings["Modules"] = {};
Settings["Modules"]["Video"] = {};
Settings["Modules"]["Video"]["Plugin"] = "JWPlayer";

However, as the number of parts that make up the setting name can vary, e.g. a newSettingName could be "Modules_Floorplan_Image_Src", I'd like to do this dynamically using a function such as...

createSetting (newSettingNameArray, newSettingValue);

function createSetting(setting, value) {
    // code to create new setting goes here
}

Can anyone help me work out how to do this dynamically?

I presume there has to be a for...loop in there to itterate through the array, but I haven't been able to work out a way to create the nested objects.

If you've got this far thanks very much for taking the time to read even if you can't help.

Cromagnon answered 30/3, 2011 at 9:45 Comment(0)
V
103
function assign(obj, keyPath, value) {
   lastKeyIndex = keyPath.length-1;
   for (var i = 0; i < lastKeyIndex; ++ i) {
     key = keyPath[i];
     if (!(key in obj)){
       obj[key] = {}
     }
     obj = obj[key];
   }
   obj[keyPath[lastKeyIndex]] = value;
}

Usage:

var settings = {};
assign(settings, ['Modules', 'Video', 'Plugin'], 'JWPlayer');
Variance answered 30/3, 2011 at 9:55 Comment(4)
Well, I wrote pretty much that same thing, except that I was going to explain newSettingName.split('_'). I don't see the point behind duplicate answers, so there. Maybe explain better your answer though.Grosvenor
what exactly are keyPath and value in that function ?Estrin
Great one. I did go to last value and hence failed. Understood, searched and found this. Being at a parent level is key and works fine.Matador
keyPath here for above example, means, the key is Modules.Video.Plugin, and value is JWPlayer, in the json they are trying to buildMatador
H
122

Put in a function, short and fast (no recursion).

var createNestedObject = function( base, names ) {
    for( var i = 0; i < names.length; i++ ) {
        base = base[ names[i] ] = base[ names[i] ] || {};
    }
};

// Usage:
createNestedObject( window, ["shapes", "triangle", "points"] );
// Now window.shapes.triangle.points is an empty object, ready to be used.

It skips already existing parts of the hierarchy. Useful if you are not sure whether the hierarchy was already created.

Or:

A fancier version where you can directly assign the value to the last object in the hierarchy, and you can chain function calls because it returns the last object.

// Function: createNestedObject( base, names[, value] )
//   base: the object on which to create the hierarchy
//   names: an array of strings contaning the names of the objects
//   value (optional): if given, will be the last object in the hierarchy
// Returns: the last object in the hierarchy
var createNestedObject = function( base, names, value ) {
    // If a value is given, remove the last name and keep it for later:
    var lastName = arguments.length === 3 ? names.pop() : false;

    // Walk the hierarchy, creating new objects where needed.
    // If the lastName was removed, then the last object is not set yet:
    for( var i = 0; i < names.length; i++ ) {
        base = base[ names[i] ] = base[ names[i] ] || {};
    }

    // If a value was given, set it to the last name:
    if( lastName ) base = base[ lastName ] = value;

    // Return the last object in the hierarchy:
    return base;
};

// Usages:

createNestedObject( window, ["shapes", "circle"] );
// Now window.shapes.circle is an empty object, ready to be used.

var obj = {}; // Works with any object other that window too
createNestedObject( obj, ["shapes", "rectangle", "width"], 300 );
// Now we have: obj.shapes.rectangle.width === 300

createNestedObject( obj, "shapes.rectangle.height".split('.'), 400 );
// Now we have: obj.shapes.rectangle.height === 400

Note: if your hierarchy needs to be built from values other that standard objects (ie. not {}), see also TimDog's answer below.

Edit: uses regular loops instead of for...in loops. It's safer in cases where a library modifies the Array prototype.

Heartstricken answered 11/7, 2012 at 12:51 Comment(12)
I hope you realize that you're answering a question for which the accepted answer is already over 15 months old...Needy
It's okay, it's just not very likely the person who asked the question is still looking for an answer. Your answer is still good (and different from the rest) so I gave you some thumbs up on it.Needy
Actually, this is the best answer as it behaves as expected.Hardtack
@Heartstricken If I need to call this from another function that handles the actual value assignment, is there a way to return the new key path instead of the key's value (base)?Engrossing
@Ken: Well, you already have the key path: its the array of names. As this function returns the last object in the chain, you only need to use the last name on the last object.Heartstricken
@Heartstricken Right, it's returning a valid path reference to a different closure that I'm having trouble with. Even if I return the path of the current iteration to the calling closure, when I try an assign a value to it, the value always gets assigned to the last object created. I have a feeling it's because I'm returning a promise, and it's getting resolved to the final object in the final iteration. So I'm looking at a refactor strategy to handle value assignments on the object buildout, like TimDog's answer below.Engrossing
I've my own recursive version for this, but +1 for avoiding recursion!Amatory
It's never too late to improve an existing answer.Gamic
@HaithamMaya: it works well too :) I edited the examples to show you.Heartstricken
@Heartstricken Thanks for this.Solute
It was my honor to give this it's 100th upvote. Haha.Bluegreen
Can you explain how this works? I understand that double assignment resolves right to left, so it is the equivalent of: base[ names[i] ] = base[ names[i] ] || {}; base = base[ names[i] ]; But logically shouldn't this overwrite base on each loop? So at the end of the loop the base object would only have the last index of [names] as a key. I would expectcreateNestedObject( obj, ["shapes", "rectangle", "width"], 300 ); to equal { 'width': 300 } but it doesn't.Den
V
103
function assign(obj, keyPath, value) {
   lastKeyIndex = keyPath.length-1;
   for (var i = 0; i < lastKeyIndex; ++ i) {
     key = keyPath[i];
     if (!(key in obj)){
       obj[key] = {}
     }
     obj = obj[key];
   }
   obj[keyPath[lastKeyIndex]] = value;
}

Usage:

var settings = {};
assign(settings, ['Modules', 'Video', 'Plugin'], 'JWPlayer');
Variance answered 30/3, 2011 at 9:55 Comment(4)
Well, I wrote pretty much that same thing, except that I was going to explain newSettingName.split('_'). I don't see the point behind duplicate answers, so there. Maybe explain better your answer though.Grosvenor
what exactly are keyPath and value in that function ?Estrin
Great one. I did go to last value and hence failed. Understood, searched and found this. Being at a parent level is key and works fine.Matador
keyPath here for above example, means, the key is Modules.Video.Plugin, and value is JWPlayer, in the json they are trying to buildMatador
H
38

My ES2015 solution. Keeps existing values.

const set = (obj, path, val) => { 
    const keys = path.split('.');
    const lastKey = keys.pop();
    const lastObj = keys.reduce((obj, key) => 
        obj[key] = obj[key] || {}, 
        obj); 
    lastObj[lastKey] = val;
};

Example:

const obj = {'a': {'prop': {'that': 'exists'}}};
set(obj, 'a.very.deep.prop', 'value');
console.log(JSON.stringify(obj));
// {"a":{"prop":{"that":"exists"},"very":{"deep":{"prop":"value"}}}}
Hanoverian answered 31/8, 2016 at 12:14 Comment(1)
it's perfect and smart thanks.Drus
T
17

Using ES6 is shorten. Set your path into an array. first, you have to reverse the array, to start filling the object.

let obj = ['a','b','c'] // {a:{b:{c:{}}}
obj.reverse();

const nestedObject = obj.reduce((prev, current) => (
    {[current]:{...prev}}
), {});
Territorial answered 5/8, 2018 at 0:22 Comment(2)
Awesome! How can this be changed if nestedObject was defined previously and has {a: {b:{}} set, but not a.b.c. ? I.e. preserving existing keys and only add missing. That would be really useful.Pegasus
could use reduceRight() instead of reverse()Ethanethane
C
11

Another recursive solution:

var nest = function(obj, keys, v) {
    if (keys.length === 1) {
      obj[keys[0]] = v;
    } else {
      var key = keys.shift();
      obj[key] = nest(typeof obj[key] === 'undefined' ? {} : obj[key], keys, v);
    }

    return obj;
};

Example usage:

var dog = {bark: {sound: 'bark!'}};
nest(dog, ['bark', 'loudness'], 66);
nest(dog, ['woff', 'sound'], 'woff!');
console.log(dog); // {bark: {loudness: 66, sound: "bark!"}, woff: {sound: "woff!"}}
Consensual answered 26/2, 2015 at 16:59 Comment(0)
P
10

I love this ES6 immutable way to set certain value on nested field:

const setValueToField = (fields, value) => {
  const reducer = (acc, item, index, arr) => ({ [item]: index + 1 < arr.length ? acc : value });
  return fields.reduceRight(reducer, {});
};

And then use it with creating your target object.

const targetObject = setValueToField(['one', 'two', 'three'], 'nice');
console.log(targetObject); // Output: { one: { two: { three: 'nice' } } }
Persinger answered 20/6, 2018 at 15:0 Comment(3)
Perfect. Thank you for this.Allinclusive
nice slick use of ES6Wenona
Good one, How do we add an array of objects ? Example { one: [ {} ,{} , {} ], two: [ {},{},{} ] }Repetitive
D
7

Here is a simple tweak to jlgrall's answer that allows setting distinct values on each element in the nested hierarchy:

var createNestedObject = function( base, names, values ) {
    for( var i in names ) base = base[ names[i] ] = base[ names[i] ] || (values[i] || {});
};

Hope it helps.

Decode answered 30/10, 2012 at 4:14 Comment(3)
Why on earth does this work and not delete existing nested objects?! You have blown minds, sir.Driskell
I know this is old, but thought I'd chime in. The reason this works is the "base" argument is passed by a copy of the reference to the original object. So on each iteration evaluated from right-to-left base becomes a pointer to the position of the currently assigned property.Integrator
I like this answer for it's simplicity once you understand what's going on, however it has a bug! Once you assign a value inside loop, tracking of the next {base} key pointer is lost and the nesting is stoped. See my answer how to assign a value to the last element onlyMiraculous
C
7

Lodash has a _.set method to achieve this

let obj = {}

_.set(obj, ['a', 'b', 'c', 'd'], 'e')

or

_.set(obj, 'a.b.c.d', 'e')

// which generate the following object
{
   "a": {
      "b": {
         "c": {
            "d": "e"
         }
      }
   }
}
Collegiate answered 15/9, 2021 at 2:28 Comment(0)
B
5

Here is a functional solution to dynamically create nested objects.

const nest = (path, obj) => {
  const reversedPath = path.split('.').reverse();

  const iter = ([head, ...tail], obj) => {
    if (!head) {
      return obj;
    }
    const newObj = {[head]: {...obj}};
    return iter(tail, newObj);
  }
  return iter(reversedPath, obj);
}

Example:

const data = {prop: 'someData'};
const path = 'a.deep.path';
const result = nest(path, data);
console.log(JSON.stringify(result));
// {"a":{"deep":{"path":{"prop":"someData"}}}}
Blondie answered 12/2, 2018 at 17:0 Comment(0)
A
3

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"}]}}}
Attach answered 28/8, 2021 at 17:58 Comment(0)
B
1

Appreciate that this question is mega old! But after coming across a need to do something like this in node, I made a module and published it to npm. Nestob

var nestob = require('nestob');

//Create a new nestable object - instead of the standard js object ({})
var newNested = new nestob.Nestable();

//Set nested object properties without having to create the objects first!
newNested.setNested('biscuits.oblong.marmaduke', 'cheese');
newNested.setNested(['orange', 'tartan', 'pipedream'], { poppers: 'astray', numbers: [123,456,789]});

console.log(newNested, newNested.orange.tartan.pipedream);
//{ biscuits: { oblong: { marmaduke: 'cheese' } },
  orange: { tartan: { pipedream: [Object] } } } { poppers: 'astray', numbers: [ 123, 456, 789 ] }

//Get nested object properties without having to worry about whether the objects exist
//Pass in a default value to be returned if desired
console.log(newNested.getNested('generic.yoghurt.asguard', 'autodrome'));
//autodrome

//You can also pass in an array containing the object keys
console.log(newNested.getNested(['chosp', 'umbridge', 'dollar'], 'symbols'));
//symbols

//You can also use nestob to modify objects not created using nestob
var normalObj = {};

nestob.setNested(normalObj, 'running.out.of', 'words');

console.log(normalObj);
//{ running: { out: { of: 'words' } } }

console.log(nestob.getNested(normalObj, 'random.things', 'indigo'));
//indigo
console.log(nestob.getNested(normalObj, 'improbable.apricots'));
//false
Beadledom answered 20/8, 2015 at 12:33 Comment(0)
P
1

Inside your loop you can use lodash.set and will create the path for you:

...
const set = require('lodash.set');

const p = {};
const [type, lang, name] = f.split('.');
set(p, [lang, type, name], '');

console.log(p);
// { lang: { 'type': { 'name': '' }}}
Prog answered 13/10, 2020 at 21:44 Comment(0)
W
0

try using recursive function:

function createSetting(setting, value, index) {
  if (typeof index !== 'number') {
    index = 0;
  }

  if (index+1 == setting.length ) {
    settings[setting[index]] = value;
  }
  else {
    settings[setting[index]] = {};
    createSetting(setting, value, ++index);
  }
}
Wigeon answered 30/3, 2011 at 10:1 Comment(0)
H
0

I think, this is shorter:

Settings = {};
newSettingName = "Modules_Floorplan_Image_Src";
newSettingValue = "JWPlayer";
newSettingNameArray = newSettingName.split("_");

a = Settings;
for (var i = 0 in newSettingNameArray) {
    var x = newSettingNameArray[i];
    a[x] = i == newSettingNameArray.length-1 ? newSettingValue : {};
    a = a[x];
}
Hesperidium answered 30/3, 2011 at 10:4 Comment(0)
S
0

I found @jlgrall's answer was great but after simplifying it, it didn't work in Chrome. Here's my fixed should anyone want a lite version:

var callback = 'fn.item1.item2.callbackfunction',
    cb = callback.split('.'),
    baseObj = window;

function createNestedObject(base, items){
    $.each(items, function(i, v){
        base = base[v] = (base[v] || {});
    });
}

callbackFunction = createNestedObject(baseObj, cb);

console.log(callbackFunction);

I hope this is useful and relevant. Sorry, I've just smashed this example out...

Southpaw answered 28/1, 2015 at 18:59 Comment(0)
S
0

You can define your own Object methods; also I'm using underscore for brevity:

var _ = require('underscore');

// a fast get method for object, by specifying an address with depth
Object.prototype.pick = function(addr) {
    if (!_.isArray(addr)) return this[addr]; // if isn't array, just get normally
    var tmpo = this;
    while (i = addr.shift())
        tmpo = tmpo[i];
    return tmpo;
};
// a fast set method for object, put value at obj[addr]
Object.prototype.put = function(addr, val) {
    if (!_.isArray(addr)) this[addr] = val; // if isn't array, just set normally
    this.pick(_.initial(addr))[_.last(addr)] = val;
};

Sample usage:

var obj = { 
           'foo': {
                   'bar': 0 }}

obj.pick('foo'); // returns { bar: 0 }
obj.pick(['foo','bar']); // returns 0
obj.put(['foo', 'bar'], -1) // obj becomes {'foo': {'bar': -1}}
Shona answered 27/5, 2015 at 15:7 Comment(0)
Z
0

A snippet for those who need to create a nested objects with support of array keys to set a value to the end of path. Path is the string like: modal.product.action.review.2.write.survey.data. Based on jlgrall version.

var updateStateQuery = function(state, path, value) {
    var names = path.split('.');
    for (var i = 0, len = names.length; i < len; i++) {
        if (i == (len - 1)) {
            state = state[names[i]] = state[names[i]] || value;
        }
        else if (parseInt(names[i+1]) >= 0) {
            state = state[names[i]] = state[names[i]] || [];
        }
        else {
            state = state[names[i]] = state[names[i]] || {};
        }
    }
};
Zonate answered 5/1, 2016 at 22:25 Comment(0)
A
0

Set Nested Data:

function setNestedData(root, path, value) {
  var paths = path.split('.');
  var last_index = paths.length - 1;
  paths.forEach(function(key, index) {
    if (!(key in root)) root[key] = {};
    if (index==last_index) root[key] = value;
    root = root[key];
  });
  return root;
}

var obj = {'existing': 'value'};
setNestedData(obj, 'animal.fish.pet', 'derp');
setNestedData(obj, 'animal.cat.pet', 'musubi');
console.log(JSON.stringify(obj));
// {"existing":"value","animal":{"fish":{"pet":"derp"},"cat":{"pet":"musubi"}}}

Get Nested Data:

function getNestedData(obj, path) {
  var index = function(obj, i) { return obj && obj[i]; };
  return path.split('.').reduce(index, obj);
}
getNestedData(obj, 'animal.cat.pet')
// "musubi"
getNestedData(obj, 'animal.dog.pet')
// undefined
Airplane answered 21/4, 2016 at 19:12 Comment(0)
N
0

Try this: https://github.com/silkyland/object-to-formdata

var obj2fd = require('obj2fd/es5').default
var fd = obj2fd({
             a:1,
             b:[
                {c: 3},
                {d: 4}
             ]
})

Result :

fd = [
       a => 1,
       b => [
         c => 3,
         d => 4
       ]
]
Nickens answered 11/8, 2017 at 10:36 Comment(0)
A
0

Here is a decomposition to several useful functions, that each preserve existing data. Does not handle arrays.

  • setDeep: Answers question. Non-destructive to other data in the object.
  • setDefaultDeep: Same, but only sets if not already set.
  • setDefault: Sets a key if not already set. Same as Python's setdefault.
  • setStructure: Helper function that builds the path.

// Create a nested structure of objects along path within obj. Only overwrites the final value.
let setDeep = (obj, path, value) =>
    setStructure(obj, path.slice(0, -1))[path[path.length - 1]] = value

// Create a nested structure of objects along path within obj. Does not overwrite any value.
let setDefaultDeep = (obj, path, value) =>
    setDefault(setStructure(obj, path.slice(0, -1)), path[path.length - 1], value)

// Set obj[key] to value if key is not in object, and return obj[key]
let setDefault = (obj, key, value) =>
    obj[key] = key in obj ? obj[key] : value;

// Create a nested structure of objects along path within obj. Does not overwrite any value.
let setStructure = (obj, path) => 
    path.reduce((obj, segment) => setDefault(obj, segment, {}), obj);



// EXAMPLES
let temp = {};

// returns the set value, similar to assignment
console.log('temp.a.b.c.d:', 
            setDeep(temp, ['a', 'b', 'c', 'd'], 'one'))

// not destructive to 'one'
setDeep(temp, ['a', 'b', 'z'], 'two')

// does not overwrite, returns previously set value
console.log('temp.a.b.z:  ', 
            setDefaultDeep(temp, ['a', 'b', 'z'], 'unused'))

// creates new, returns current value
console.log('temp["a.1"]: ', 
            setDefault(temp, 'a.1', 'three'))

// can also be used as a getter
console.log("temp.x.y.z:  ", 
            setStructure(temp, ['x', 'y', 'z']))


console.log("final object:", temp)

I'm not sure why anyone would want string paths:

  1. They are ambiguous for keys with periods
  2. You have to build the strings in the first place
Acrylonitrile answered 14/10, 2020 at 22:37 Comment(0)
B
0

Since I started with something from this page, I wanted to contribute back

Other examples overwrote the final node even if it was set, and that wasn't what I wanted.

Also, if returnObj is set to true, it returns the base object. By default, falsy, it returns the deepest node.

function param(obj, path, value, returnObj) {
  if (typeof path == 'string') path = path.split(".");
  var child = obj;
  path.forEach((key, i) => {
    if (!(key in child)) {
      child[key] = (i < path.length-1) ? {} : value || {};
    }
    child = child[key];
  });
  return returnObj ? obj : child;
}

var x = {};
var xOut = param(x, "y.z", "setting")
console.log(xOut);
xOut = param(x, "y.z", "overwrite") // won't set
console.log(xOut);
xOut = param(x, "y.a", "setting2")
console.log(xOut);
xOut = param(x, "y.a", "setting2", true) // get object rather than deepest node.
console.log(xOut);

You can also do something where numeric keys are placed in arrays (if they don't already exist). Note that numeric keys won't convert to arrays for the first element of the path, since that's set by the type of your base-object.

function isNumber(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

function param(obj, path, value, returnObj) {
  if (typeof path == 'string') path = path.split(".");
  var child = obj;
  path.forEach((key, i) => {
    var nextKey = path[i+1];
    if (!(key in child)) {
      child[key] = (nextKey == undefined && value != undefined 
        ? value 
        : isNumber(nextKey)
          ? []
          : {});
    }
    child = child[key];
  });
  return returnObj ? obj : child;
}

var x = {};

var xOut = param(x, "y.z", "setting")
console.log(xOut);
xOut = param(x, "y.z", "overwrite") // won't set
console.log(xOut);
xOut = param(x, "y.a", "setting2")
console.log(xOut);
xOut = param(x, "y.a", "setting2", true) // get object rather than deepest node.
xOut = param(x, "1.0.2.a", "setting")
xOut = param(x, "1.0.1.a", "try to override") // won't set
xOut = param(x, "1.0.5.a", "new-setting", true) // get object rather than deepest node.
console.log(xOut);

Naturally, when the numeric keys are greater than 0, you might see some undefined gaps.

Practical uses of this might be

function AddNote(book, page, line) {
  // assume a global global notes collection
  var myNotes = param(allNotes, [book, page, line], []);
  myNotes.push('This was a great twist!')
  return myNotes;
}

var allNotes = {}
var youthfulHopes = AddNote('A Game of Thrones', 4, 2, "I'm already hooked, at least I won't have to wait long for the books to come out!");

console.log(allNotes)
// {"A Game of Thrones": [undefined, undefined, undefined, undefined, [undefined, undefined, ["I'm already hooked, at least I won't have to wait long for the books to come out!"]]]}
console.log(youthfulHopes)
// ["I'm already hooked, at least I won't have to wait long for the books to come out!"]
Bluegreen answered 7/6, 2021 at 18:23 Comment(0)
E
0
function initPath(obj, path) {
  path.split('.').reduce((o, key) => (
    Object.assign(o, {[key]: Object(o[key])}),
    o[key]
  ), obj);
  return obj;
}

Usage

const obj = { a: { b: 'value1' } };
initPath(obj, 'a.c.d').a.c.d='value2';
/*
{
  "a": {
    "b": "value1",
    "c": {
      "d": "value2"
    }
  }
}
*/
Escalera answered 26/8, 2021 at 14:46 Comment(0)
M
0

simple answer. on es6, im using this

const assign = (obj, path, value) => {
  let keyPath = path.split('.')
  let lastKeyIndex = keyPath.length - 1
  for (let i = 0; i < lastKeyIndex; ++i) {
    let key = keyPath[i]
    if (!(key in obj)) {
      obj[key] = {}
    }
    obj = obj[key]
  }
  obj[keyPath[lastKeyIndex]] = value
}

example json

const obj = {
  b: 'hello'
}

you can add new key

assign(obj, 'c.d.e', 'this value')

and you get like bellow

console.log(obj)
//response example
obj = {
   b: 'hello',
   c: {
      d: {
         e: 'this value'
      }
   }
}
Moreno answered 10/11, 2021 at 17:30 Comment(0)
H
0

function createObj(keys, value) {
  let obj = {}
  let schema = obj
  keys = keys.split('.')

  for (let i = 0; i < keys.length - 1; i++) {
    schema[keys[i]] = {}
    schema = schema[keys[i]]
  }

  schema[keys.pop()] = value

  return obj
}


let keys = 'value1.value2.value3'
let value = 'Hello'

let obj = createObj(keys, value)
Hearts answered 15/12, 2021 at 14:24 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Uxmal
M
0

Followed by TimDog's answer this adds value to last element only

function createNestedObject2(base, names, value = null) {
    for (let i = 0; i < names.length; i++) {
        base = base[names[i]] = i == names.length - 1 ? value : {};
    }
}

var obj = {};
createNestedObject2(obj, ["a", "b", "c"], "val");
console.log(JSON.stringify(obj, null, 4));

Result:

{
"a": {
    "b": {
        "c": "val"
    }
}
Miraculous answered 9/7, 2023 at 12:40 Comment(0)
B
-1

Eval is probably overkill but the result is simple to visualize, with no nested loops or recursion.

 function buildDir(obj, path){
   var paths = path.split('_');
   var final = paths.pop();
   for (let i = 1; i <= paths.length; i++) {
     var key = "obj['" + paths.slice(0, i).join("']['") + "']"
     console.log(key)
     eval(`${key} = {}`)
   }
   eval(`${key} = '${final}'`)
   return obj
 }

 var newSettingName = "Modules_Video_Plugin_JWPlayer";
 var Settings = buildDir( {}, newSettingName );

Basically you are progressively writing a string "obj['one']= {}", "obj['one']['two']"= {} and evaling it;

Boundless answered 19/1, 2017 at 20:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.