web-dev-qa-db-fra.com

Javascript: comment créer dynamiquement des objets imbriqués à l'aide de noms d'objets donnés par un tableau

J'espère que quelqu'un pourra m'aider avec ce Javascript.

J'ai un objet appelé "Paramètres" et je voudrais écrire une fonction qui ajoute de nouveaux paramètres à cet objet.

Le nom et la valeur du nouveau paramètre sont fournis sous forme de chaînes. La chaîne donnant le nom du paramètre est ensuite divisée par les traits de soulignement en un tableau. Le nouveau paramètre doit être ajouté à l'objet "Paramètres" existant en créant de nouveaux objets imbriqués avec les noms donnés par chaque partie du tableau, à l'exception de la dernière partie qui doit être une chaîne donnant la valeur du paramètre. Je devrais alors pouvoir me référer au paramètre et par exemple alerter sa valeur. Je peux le faire de manière statique comme ça ...

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);

... la partie qui crée les objets imbriqués fait cela ...

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

Cependant, comme le nombre de pièces qui composent le nom du paramètre peut varier, par ex. un nouveauSettingName pourrait être "Modules_Floorplan_Image_Src", je voudrais le faire dynamiquement en utilisant une fonction telle que ...

createSetting (newSettingNameArray, newSettingValue);

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

Quelqu'un peut-il m'aider à déterminer comment procéder de manière dynamique?

Je suppose qu'il doit y avoir une boucle for ... pour parcourir le tableau, mais je n'ai pas réussi à trouver un moyen de créer les objets imbriqués.

Si vous êtes arrivé jusqu'ici, merci beaucoup d'avoir pris le temps de lire même si vous ne pouvez pas aider.

54
David
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');
63
kennytm

Ma solution ES2015. Conserve les valeurs existantes.

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;
};

Exemple:

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"}}}}
12
Laurens

Une autre solution récursive:

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;
};

Exemple d'utilisation:

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!"}}
8
nerfologist

L'utilisation d'ES6 est raccourcie. Définissez votre chemin dans un tableau. vous devez d'abord inverser le tableau pour commencer à remplir l'objet.

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

const nestedObject = obj.reduce((prev, current) => (
    {[current]:{...prev}}
), {});
7
Diego Molina

Voici un simple tweak à la réponse de jlgrall qui permet de définir des valeurs distinctes sur chaque élément de la hiérarchie imbriquée:

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

J'espère que ça aide.

5
TimDog

Voici une solution fonctionnelle pour créer dynamiquement des objets imbriqués.

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);
}

Exemple:

const data = {prop: 'someData'};
const path = 'a.deep.path';
const result = nest(path, data);
console.log(JSON.stringify(result));
// {"a":{"deep":{"path":{"prop":"someData"}}}}
4
Aliaksandr Sushkevich

J'adore cette façon immuable ES6 de définir une certaine valeur sur un champ imbriqué:

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

Et puis utilisez-le pour créer votre objet cible.

const targetObject = setValueToField(['one', 'two', 'three'], 'Nice');
console.log(targetObject); // Output: { one: { two: { three: 'Nice' } } }
1
Vadim Shvetsov

Appréciez que cette question soit méga ancienne! Mais après avoir rencontré un besoin de faire quelque chose comme ça dans le nœud, j'ai créé un module et l'ai publié sur 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
1
Chrift

essayez d'utiliser la fonction récursive:

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);
  }
}
0
sv_in

Eval est probablement exagéré mais le résultat est simple à visualiser, sans boucles imbriquées ni récursivité.

 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 );

En gros, vous écrivez progressivement une chaîne "obj['one']= {}", "obj['one']['two']"= {} et l'évaluer;

0
lonewarrior556

Je pense que c'est plus court:

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];
}
0
Deele

J'ai trouvé que la réponse de @ jlgrall était excellente, mais après l'avoir simplifiée, cela n'a pas fonctionné dans Chrome. Voici ma correction si quelqu'un veut une version allégée:

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);

J'espère que cela est utile et pertinent. Désolé, je viens d'écraser cet exemple ...

0
Must Impress

Vous pouvez définir vos propres méthodes Object; aussi j'utilise le soulignement pour plus de concision:

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;
};

Exemple d'utilisation:

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}}
0
Keng

Essayez ceci: https://github.com/silkyland/object-to-formdata

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

Résultat :

fd = [
       a => 1,
       b => [
         c => 3,
         d => 4
       ]
]
0
Manioz

Un extrait pour ceux qui ont besoin de créer des objets imbriqués avec prise en charge des clés de tableau pour définir une valeur à la fin du chemin. Le chemin est la chaîne comme: modal.product.action.review.2.write.survey.data. Basé sur la version jlgrall.

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]] || {};
        }
    }
};
0

Définir les données imbriquées:

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"}}}

Obtenir les données imbriquées:

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
0
Cody Moniz