145 votes

Définir dynamiquement la propriété d'un objet imbriqué

J'ai un objet qui pourrait être de n'importe quel niveau de profondeur et pourrait avoir n'importe quelles propriétés existantes. Par exemple:

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

Sur cela, j'aimerais définir (ou écraser) des propriétés de cette manière:

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

Où la chaîne de propriété peut avoir n'importe quelle profondeur, et la valeur peut être de n'importe quel type/chose.
Les objets et les tableaux en tant que valeurs n'ont pas besoin d'être fusionnés, si la clé de propriété existe déjà.

L'exemple précédent produirait l'objet suivant:

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

Comment puis-je réaliser une telle fonction?

141voto

bpmason1 Points 1690

Cette fonction, en utilisant les arguments que vous avez spécifiés, devrait ajouter/mettre à jour les données dans le conteneur obj. Notez que vous devez suivre les éléments du schéma de obj qui sont des conteneurs et ceux qui sont des valeurs (chaînes de caractères, entiers, etc.), sinon vous commencerez à obtenir des exceptions.

obj = {};  // objet global

function set(path, value) {
    var schema = obj;  // une référence mobile aux objets internes de 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');

125voto

user361566 Points 1

Lodash a une méthode _.set().

_.set(obj, 'db.mongodb.user', 'root');
_.set(obj, 'foo.bar', 'baz');

28voto

Bruno Joaquim Points 732

J'ai simplement écrit une petite fonction en utilisant ES6 + récursion pour atteindre l'objectif.

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

Je l'ai beaucoup utilisé sur react pour mettre à jour l'état, ça a très bien fonctionné pour moi.

25voto

Felipe Tadeo Points 623

Un peu tard mais voici une réponse plus simple sans bibliothèque :

/**
 * Définit de manière dynamique une valeur profondément imbriquée dans un objet.
 * Crée éventuellement un chemin vers celle-ci si elle est indéfinie.
 * @function
 * @param {!object} obj  - L'objet qui contient la valeur que vous voulez changer/définir.
 * @param {!array} path  - La représentation de tableau du chemin vers la valeur que vous voulez changer/définir.
 * @param {!mixed} value - La valeur que vous voulez lui attribuer.
 * @param {boolean} setrecursively - Si true, définira également la valeur du chemin inexistant.
 */
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);
}

Cette fonction que j'ai créée peut faire exactement ce dont vous avez besoin et un peu plus.

disons que nous voulons changer la valeur cible qui est profondément imbriquée dans cet objet :

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

Donc nous appellerions notre fonction comme ceci :

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

résultera en :

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

En définissant le drapeau setrecursively sur true, des objets seront définis s'ils n'existent pas.

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

résultera en ceci :

obj = myObj = {
    new: {
         path: {
             target: 3
         }
    },
    level1: {
        level2: {
           target: 3
       }
    }
}

23voto

Hemã Vidal Points 385

Nous pouvons utiliser une fonction de récursion :

/**
 * Définit une valeur de la clé imbriquée de la chaîne de descripteur à l'intérieur d'un objet.
 * Il modifie l'objet passé.
 * Ex :
 *    let obj = {a: {b:{c:'initial'}}}
 *    setNestedKey(obj, ['a', 'b', 'c'], 'valeur-modifiée')
 *    assert(obj === {a: {b:{c:'valeur-modifiée'}}})
 *
 * @param {[Object]} obj   Objet pour définir la clé imbriquée
 * @param {[Array]} path  Un tableau pour décrire le chemin (Ex : ['a', 'b', 'c'])
 * @param {[Object]} value N'importe quelle valeur
 */
export const setNestedKey = (obj, path, value) => {
  if (path.length === 1) {
    obj[path] = value
    return
  }
  return setNestedKey(obj[path[0]], path.slice(1), value)
}

C'est plus simple !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X