Comment fusionner dynamiquement les propriétés de deux objets JavaScript?


2522

J'ai besoin de pouvoir fusionner deux (très simples) objets JavaScript au moment de l'exécution. Par exemple, je voudrais:

var obj1 = { food: 'pizza', car: 'ford' }
var obj2 = { animal: 'dog' }

obj1.merge(obj2);

//obj1 now has three properties: food, car, and animal

Quelqu'un at-il un script pour cela ou connaît-il une manière intégrée de le faire? Je n'ai pas besoin de récursivité, et je n'ai pas besoin de fusionner de fonctions, juste des méthodes sur des objets plats.


Il vaut la peine de noter cette réponse sur une question similaire , qui montre comment fusionner "un niveau plus bas". C'est-à-dire qu'il fusionne les valeurs des clés en double (au lieu de remplacer la première valeur par la deuxième valeur), mais ne récapitule pas plus loin que cela. À mon humble avis, son bon code propre pour cette tâche.
ToolmakerSteve

BTW, les quelques premières réponses font une fusion "superficielle": si la même clé existe à la fois dans obj1 et obj2, la valeur dans obj2 est conservée, la valeur dans obj1 est supprimée. Par exemple, si l'exemple de la question avait var obj2 = { animal: 'dog', food: 'bone' };, la fusion serait { food: 'bone', car: 'ford', animal: 'dog' }. Si vous travaillez avec des "données imbriquées" et souhaitez une "fusion profonde", recherchez les réponses qui mentionnent "fusion profonde" ou "récursivité". Si vous avez des valeurs qui sont arrays, alors utilisez l'option "arrayMerge" du github "TehShrike / deepmerge", comme mentionné ici .
ToolmakerSteve

Veuillez suivre le lien ci-dessous stackoverflow.com/questions/171251/… L'opérateur de propagation qui est la nouvelle fonctionnalité de la version
ES6

Réponses:


2910

Méthode standard ECMAScript 2018

Vous utiliseriez la propagation d'objets :

let merged = {...obj1, ...obj2};

mergedest maintenant l'union de obj1et obj2. Les propriétés de obj2remplaceront celles de obj1.

/** There's no limit to the number of objects you can merge.
 *  Later properties overwrite earlier properties with the same name. */
const allRules = {...obj1, ...obj2, ...obj3};

Voici également la documentation MDN de cette syntaxe. Si vous utilisez babel, vous aurez besoin du plugin babel-plugin-transform-object-rest-spread pour que cela fonctionne.

ECMAScript 2015 (ES6) Méthode standard

/* For the case in question, you would do: */
Object.assign(obj1, obj2);

/** There's no limit to the number of objects you can merge.
 *  All objects get merged into the first object. 
 *  Only the object in the first argument is mutated and returned.
 *  Later properties overwrite earlier properties with the same name. */
const allRules = Object.assign({}, obj1, obj2, obj3, etc);

(voir référence JavaScript MDN )


Méthode pour ES5 et versions antérieures

for (var attrname in obj2) { obj1[attrname] = obj2[attrname]; }

Notez que cela simplement ajouter tous les attributs de obj2à obj1qui pourrait ne pas être ce que vous voulez si vous voulez continuer à utiliser la non modifiée obj1.

Si vous utilisez un cadre qui craque partout dans vos prototypes, vous devez devenir plus amateur avec des vérifications comme hasOwnProperty, mais ce code fonctionnera dans 99% des cas.

Exemple de fonction:

/**
 * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
 * @param obj1
 * @param obj2
 * @returns obj3 a new object based on obj1 and obj2
 */
function merge_options(obj1,obj2){
    var obj3 = {};
    for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
    for (var attrname in obj2) { obj3[attrname] = obj2[attrname]; }
    return obj3;
}

90
Cela ne fonctionne pas si les objets ont des attributs de même nom et que vous souhaitez également fusionner les attributs.
Xiè Jìléi

69
Cela ne fait qu'une copie / fusion superficielle. A le potentiel de s'encombrer de nombreux éléments.
Jay Taylor

45
+1 pour avoir reconnu que certaines pauvres âmes sont obligées d'utiliser des frameworks qui merdent partout dans leurs prototypes ...
thejonwithnoh

4
Cela n'a pas fonctionné pour moi car "Par conséquent, il attribue des propriétés par rapport à la simple copie ou définition de nouvelles propriétés. Cela peut le rendre inapproprié pour la fusion de nouvelles propriétés dans un prototype si les sources de fusion contiennent des getters." ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ). Je devais utiliser var merged = {...obj1, ...obj2}.
morgler

2
@ Ze'ev{...obj1, ...{animal: 'dog'}}
backslash112

1191

jQuery a également un utilitaire pour cela: http://api.jquery.com/jQuery.extend/ .

Tiré de la documentation jQuery:

// Merge options object into settings object
var settings = { validate: false, limit: 5, name: "foo" };
var options  = { validate: true, name: "bar" };
jQuery.extend(settings, options);

// Now the content of settings object is the following:
// { validate: true, limit: 5, name: "bar" }

Le code ci-dessus mute l' objet existant nommé settings.


Si vous souhaitez créer un nouvel objet sans modifier aucun des arguments, utilisez ceci:

var defaults = { validate: false, limit: 5, name: "foo" };
var options = { validate: true, name: "bar" };

/* Merge defaults and options, without modifying defaults */
var settings = $.extend({}, defaults, options);

// The content of settings variable is now the following:
// {validate: true, limit: 5, name: "bar"}
// The 'defaults' and 'options' variables remained the same.

166
Attention: la variable "settings" sera cependant modifiée. jQuery ne renvoie pas de nouvelle instance. La raison de cela (et de la dénomination) est que .extend () a été développé pour étendre des objets, plutôt que pour fusionner des objets. Si vous voulez un nouvel objet (par exemple, les paramètres sont des valeurs par défaut que vous ne voulez pas toucher), vous pouvez toujours jQuery.extend ({}, paramètres, options);
webmat

1
Tout de suite! Merci beaucoup. Comme indiqué précédemment, il est mal nommé. J'ai recherché des documents jQuery en arrière et quatrième et ne l'ai pas rencontré.
Mike Starov

28
Attention, jQuery.extend a également un paramètre profond (booléen). jQuery.extend(true,settings,override), ce qui est important si une propriété des paramètres contient un objet et que la substitution n'a qu'une partie de cet objet. Au lieu de supprimer les propriétés inégalées, le paramètre profond ne se mettra à jour que là où il existe. Le défaut est faux.
vol7ron

19
Gee willikers, ils ont vraiment fait un bon travail en nommant cela. Si vous recherchez comment étendre un objet Javascript, cela arrive tout de suite! Vous pourriez ne pas le toucher si vous essayez de fusionner ou d'assembler des objets Javascript.
Derek Greer

2
Ne dites pas aux gens d'envisager d'utiliser une méthode de bibliothèque quand quelques lignes de vanilla JS feront ce qu'elles veulent. Il fut un temps avant jQuery!
CrazyMerlin

348

Le Harmony ECMAScript 2015 (ES6) spécifie Object.assignqui le fera.

Object.assign(obj1, obj2);

La prise en charge actuelle des navigateurs s'améliore , mais si vous développez pour des navigateurs non pris en charge, vous pouvez utiliser un polyfill .


35
Notez que ce n'est qu'une fusion superficielle
Joachim Lous

Je pense que pendant ce temps Object.assigna une couverture assez décente: kangax.github.io/compat-table/es6/…
LazerBass

13
Cela devrait maintenant être la bonne réponse. De nos jours, les gens compilent leur code pour être compatible avec le navigateur rare (IE11) qui ne prend pas en charge ce genre de chose. Note latérale: si vous ne voulez pas ajouter obj2 à obj1, vous pouvez renvoyer un nouvel objet en utilisantObject.assign({}, obj1, obj2)
Duvrai

6
@Duvrai Selon les rapports que j'ai vus, IE11 n'est certainement pas rare à environ 18% de la part de marché en juillet 2016.
NanoWizard

3
@Kermani L'OP souligne spécifiquement que la récursivité n'est pas nécessaire. Par conséquent, bien qu'il soit utile de savoir que cette solution effectue une fusion superficielle, cette réponse est suffisante telle quelle. Le commentaire de JoachimLou a reçu des votes positifs bien mérités et fournit ces informations supplémentaires en cas de besoin. Je pense que la différence entre une fusion profonde et superficielle et des sujets similaires dépasse la portée de cette discussion et est mieux adaptée à d'autres questions SO.
NanoWizard

264

J'ai recherché du code pour fusionner les propriétés des objets sur Google et je me suis retrouvé ici. Cependant, comme il n'y avait pas de code pour la fusion récursive, je l'ai écrit moi-même. (Peut-être que jQuery extend est récursif BTW?) Quoi qu'il en soit, j'espère que quelqu'un d'autre le trouvera également utile.

(Maintenant, le code n'utilise pas Object.prototype:)

Code

/*
* Recursively merge properties of two objects 
*/
function MergeRecursive(obj1, obj2) {

  for (var p in obj2) {
    try {
      // Property in destination object set; update its value.
      if ( obj2[p].constructor==Object ) {
        obj1[p] = MergeRecursive(obj1[p], obj2[p]);

      } else {
        obj1[p] = obj2[p];

      }

    } catch(e) {
      // Property in destination object not set; create it and set its value.
      obj1[p] = obj2[p];

    }
  }

  return obj1;
}

Un exemple

o1 = {  a : 1,
        b : 2,
        c : {
          ca : 1,
          cb : 2,
          cc : {
            cca : 100,
            ccb : 200 } } };

o2 = {  a : 10,
        c : {
          ca : 10,
          cb : 20, 
          cc : {
            cca : 101,
            ccb : 202 } } };

o3 = MergeRecursive(o1, o2);

Produit un objet o3 comme

o3 = {  a : 10,
        b : 2,
        c : {
          ca : 10,
          cb : 20,
          cc : { 
            cca : 101,
            ccb : 202 } } };

11
Bien, mais je ferais d'abord une copie profonde des objets. De cette façon, o1 serait également modifié, car les objets sont passés par référence.
skerit du

1
C'était ce que je cherchais. Assurez-vous de vérifier si obj2.hasOwnProperty (p) avant de passer à l'instruction try catch. Sinon, vous pourriez vous retrouver avec d'autres conneries fusionnées de plus haut dans la chaîne de prototypes.
Adam

3
Merci Markus. Notez que o1 est également modifié par MergeRecursive, donc à la fin, o1 et o3 sont identiques. Cela peut être plus intuitif si vous ne retournez rien, afin que l'utilisateur sache que ce qu'il fournit (o1) est modifié et devient le résultat. De plus, dans votre exemple, il pourrait être utile d'ajouter quelque chose dans o2 qui n'est pas à l'origine dans o1, pour montrer que les propriétés o2 sont insérées dans o1 (quelqu'un d'autre ci-dessous a soumis un code alternatif copiant votre exemple, mais le leur se rompt lorsque o2 contient des propriétés non en o1 - mais le vôtre fonctionne à cet égard).
crêpe

7
C'est une bonne réponse, mais quelques problèmes: 1) Aucune logique ne devrait être basée sur des exceptions; dans ce cas, toute exception levée sera prise avec des résultats imprévisibles. 2) Je suis d'accord avec le commentaire de @ pancake sur le fait de ne rien retourner, car l'objet d'origine est modifié. Voici un exemple jsFiddle sans la logique d'exception: jsfiddle.net/jlowery2663/z8at6knn/4 - Jeff Lowery
Jeff Lowery

3
De plus, obj2 [p] .constructor == Un tableau est nécessaire dans tous les cas où il existe un tableau d'objets.
The Composer

175

Notez que underscore.jsla extendméthode 's fait cela dans une seule ligne:

_.extend({name : 'moe'}, {age : 50});
=> {name : 'moe', age : 50}

36
Cet exemple est parfait car vous avez affaire à des objets anonymes, mais si ce n'est pas le cas, le commentaire du webmat dans l' avertissement de réponse jQuery sur les mutations s'applique ici, car le soulignement mute également l'objet de destination . Comme la réponse jQuery, pour faire cela sans mutation, il suffit de fusionner en un objet vide:_({}).extend(obj1, obj2);
Abe Voelker

8
Il y en a un autre qui pourrait être pertinent en fonction de ce que vous essayez de réaliser: _.defaults(object, *defaults)"Remplissez les propriétés nulles et non définies dans l'objet avec les valeurs des objets par défaut et renvoyez l'objet."
conny

Beaucoup de gens ont commenté la mutation de l'objet de destination, mais plutôt que d'avoir une méthode pour en créer un nouveau, un modèle courant (dans dojo par exemple) est de dire dojo.mixin (dojo.clone (myobj), {newpropertys: "to add à myobj "})
Colin D

7
Le soulignement peut prendre un nombre arbitraire d'objets, vous pouvez donc éviter la mutation de l'objet d'origine en faisant var mergedObj = _.extend({}, obj1, obj2).
lobati

84

Semblable à jQuery extend (), vous avez la même fonction dans AngularJS :

// Merge the 'options' object into the 'settings' object
var settings = {validate: false, limit: 5, name: "foo"};
var options  = {validate: true, name: "bar"};
angular.extend(settings, options);

1
@naXa Je pense que ce serait une option si vous ne voulez pas ajouter une bibliothèque juste pour ce fn.
juanmf

67

J'ai besoin de fusionner des objets aujourd'hui, et cette question (et ses réponses) m'a beaucoup aidé. J'ai essayé certaines des réponses, mais aucune d'entre elles ne correspondait à mes besoins, j'ai donc combiné certaines des réponses, ajouté moi-même quelque chose et proposé une nouvelle fonction de fusion. C'est ici:

var merge = function() {
    var obj = {},
        i = 0,
        il = arguments.length,
        key;
    for (; i < il; i++) {
        for (key in arguments[i]) {
            if (arguments[i].hasOwnProperty(key)) {
                obj[key] = arguments[i][key];
            }
        }
    }
    return obj;
};

Quelques exemples d'utilisations:

var t1 = {
    key1: 1,
    key2: "test",
    key3: [5, 2, 76, 21]
};
var t2 = {
    key1: {
        ik1: "hello",
        ik2: "world",
        ik3: 3
    }
};
var t3 = {
    key2: 3,
    key3: {
        t1: 1,
        t2: 2,
        t3: {
            a1: 1,
            a2: 3,
            a4: [21, 3, 42, "asd"]
        }
    }
};

console.log(merge(t1, t2));
console.log(merge(t1, t3));
console.log(merge(t2, t3));
console.log(merge(t1, t2, t3));
console.log(merge({}, t1, { key1: 1 }));

3
bonne réponse mais je pense que si une propriété est présente dans le premier et dans le second et que vous essayez de créer un nouvel objet en fusionnant le premier et le second, alors les uniques présents dans le premier objet seront perdus
Strikers

2
Mais n'est-ce pas un comportement attendu? Le but de cette fonction est de remplacer les valeurs dans l'ordre des arguments. Le suivant remplacera le courant. Comment pouvez-vous préserver des valeurs uniques? D'après mon exemple, qu'attendez-vous de merge({}, t1, { key1: 1 })?
Emre Erkan

Mon attente est de fusionner uniquement les valeurs non typées objet.
AGamePlayer

échoue si le résultat attendu est le suivant: gist.github.com/ceremcem/a54f90923c6e6ec42c7daf24cf2768bd
ceremcem

Cela a fonctionné pour moi en mode strict pour mon projet node.js. Jusqu'à présent, c'est la meilleure réponse sur ce post.
klewis

48

Vous pouvez utiliser les propriétés de répartition des objets - actuellement une proposition ECMAScript d'étape 3.

const obj1 = { food: 'pizza', car: 'ford' };
const obj2 = { animal: 'dog' };

const obj3 = { ...obj1, ...obj2 };
console.log(obj3);


Prise en charge du navigateur?
Alph.Dev

1
@ Alph.Dev Vous connaissez caniuse.com?
connexo

Je n'arrive pas à faire fonctionner la syntaxe à 3 points dans node.js. le nœud s'en plaint.
klewis

41

Les solutions données doivent être modifiées pour archiver source.hasOwnProperty(property)les for..inboucles avant d'affecter - sinon, vous finissez par copier les propriétés de l'ensemble de la chaîne de prototypes, ce qui est rarement souhaité ...


40

Fusionner les propriétés de N objets dans une seule ligne de code

Une Object.assignméthode fait partie de la norme ECMAScript 2015 (ES6) et fait exactement ce dont vous avez besoin. ( IEnon pris en charge)

var clone = Object.assign({}, obj);

La méthode Object.assign () est utilisée pour copier les valeurs de toutes les propriétés propres énumérables d'un ou plusieurs objets source vers un objet cible.

Lire la suite...

Le polyfill pour supporter les anciens navigateurs:

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}

qu'est-ce que 'use strict' fait dans les anciens navigateursm ou pourquoi est-il là? navigateurs prenant en charge object [defineProperty; clés; getOwnPropertyDescriptor; etc], ne sont pas exactement vieux. Ce ne sont que des versions antérieures des UA de génération 5.
Bekim Bacaj

Merci d'avoir clarifié ce qui Objects.assignrenvoie un objet fusionné auquel les autres réponses ne répondent pas. C'est un style de programmation plus fonctionnel.
Sridhar Sarnobat

36

Les deux suivants sont probablement un bon point de départ. lodash a également une fonction de personnalisation pour ces besoins spéciaux!

_.extend( http://underscorejs.org/#extend )
_.merge( https://lodash.com/docs#merge )


4
Veuillez noter que les méthodes ci-dessus mutent l'objet d'origine.
Wtower

La méthode lodash a fonctionné pour moi car je voulais fusionner deux objets et conserver les propriétés imbriquées à plus d'un ou deux niveaux de profondeur (plutôt que de simplement les remplacer / les essuyer).
SamBrick

vrai @Wtower. J'ai juste eu un bug à cause de ça. Assurez-vous qu'il commence toujours comme _.merge ({}, ...
Martin Cremer

29

Soit dit en passant, ce que vous faites, c'est écraser les propriétés, pas fusionner ...

C'est ainsi que la zone des objets JavaScript a vraiment fusionné: seules les clés de l' toobjet qui ne sont pas des objets eux-mêmes seront écrasées par from. Tout le reste sera vraiment fusionné . Bien sûr, vous pouvez modifier ce comportement pour ne pas écraser tout ce qui existe comme si to[n] is undefined, etc ...:

var realMerge = function (to, from) {

    for (n in from) {

        if (typeof to[n] != 'object') {
            to[n] = from[n];
        } else if (typeof from[n] == 'object') {
            to[n] = realMerge(to[n], from[n]);
        }
    }
    return to;
};

Usage:

var merged = realMerge(obj1, obj2);

3
M'a beaucoup aidé, le prolongement de js de soulignement a effectivement écrasé, au lieu de fusionner
David Cumps

1
Il convient de noter que typeof array et typeof null renverront «objet» et rompront cela.
Salakar

@ u01jmg3 voir ma réponse ici: stackoverflow.com/questions/27936772/… - la fonction
isObject

Cela casse si vous utilisez est en mode strict pour les projets node.js
klewis

28

Voici mon coup de couteau qui

  1. Prend en charge la fusion profonde
  2. Ne mute pas les arguments
  3. Prend n'importe quel nombre d'arguments
  4. N'étend pas le prototype d'objet
  5. Ne dépend pas d'une autre bibliothèque ( jQuery , MooTools , Underscore.js , etc.)
  6. Comprend la vérification de hasOwnProperty
  7. C'est court :)

    /*
        Recursively merge properties and return new object
        obj1 &lt;- obj2 [ &lt;- ... ]
    */
    function merge () {
        var dst = {}
            ,src
            ,p
            ,args = [].splice.call(arguments, 0)
        ;
    
        while (args.length > 0) {
            src = args.splice(0, 1)[0];
            if (toString.call(src) == '[object Object]') {
                for (p in src) {
                    if (src.hasOwnProperty(p)) {
                        if (toString.call(src[p]) == '[object Object]') {
                            dst[p] = merge(dst[p] || {}, src[p]);
                        } else {
                            dst[p] = src[p];
                        }
                    }
                }
            }
        }
    
       return dst;
    }

Exemple:

a = {
    "p1": "p1a",
    "p2": [
        "a",
        "b",
        "c"
    ],
    "p3": true,
    "p5": null,
    "p6": {
        "p61": "p61a",
        "p62": "p62a",
        "p63": [
            "aa",
            "bb",
            "cc"
        ],
        "p64": {
            "p641": "p641a"
        }
    }
};

b = {
    "p1": "p1b",
    "p2": [
        "d",
        "e",
        "f"
    ],
    "p3": false,
    "p4": true,
    "p6": {
        "p61": "p61b",
        "p64": {
            "p642": "p642b"
        }
    }
};

c = {
    "p1": "p1c",
    "p3": null,
    "p6": {
        "p62": "p62c",
        "p64": {
            "p643": "p641c"
        }
    }
};

d = merge(a, b, c);


/*
    d = {
        "p1": "p1c",
        "p2": [
            "d",
            "e",
            "f"
        ],
        "p3": null,
        "p5": null,
        "p6": {
            "p61": "p61b",
            "p62": "p62c",
            "p63": [
                "aa",
                "bb",
                "cc"
            ],
            "p64": {
                "p641": "p641a",
                "p642": "p642b",
                "p643": "p641c"
            }
        },
        "p4": true
    };
*/

Comparée aux autres que j'ai testées sur cette page, cette fonction est vraiment récursive (fait une fusion profonde) et imite ce que jQuery.extend () fait vraiment bien. Cependant, il serait préférable qu'il modifie le premier objet / argument afin qu'un utilisateur puisse décider s'il souhaite passer un objet vide {}comme premier paramètre ou que la fonction modifie l'objet d'origine. Par conséquent , si vous changez dst = {}de dst = arguments[0]cela va changer le premier objet que vous passez à l'objet fusionné
Jasdeep Khalsa

3
Maintenant, il ne fonctionne plus en chrome. Je pense que c'est une mauvaise idée d'utiliser une telle construction toString.call(src) == '[object Object]'pour vérifier s'il y a un objet ou non. typeof srcc'est beaucoup mieux. De plus, il transforme les tableaux en objets (au moins, j'ai un tel comportement sur le dernier chrome).
m03geek

J'avais besoin de le faire dans une boucle gourmande, j'ai comparé cette fonction à celle que j'utilisais jusqu'ici _.merge (objet, [sources]) de lodash (une sorte de lib de soulignement): votre fonction est presque deux fois plus rapide Merci mon pote.
Arnaud Bouchot

20

Object.assign ()

ECMAScript 2015 (ES6)

Il s'agit d'une nouvelle technologie, faisant partie de la norme ECMAScript 2015 (ES6). La spécification de cette technologie a été finalisée, mais vérifiez le tableau de compatibilité pour l'état d'utilisation et d'implémentation dans divers navigateurs.

La méthode Object.assign () est utilisée pour copier les valeurs de toutes les propriétés propres énumérables d'un ou plusieurs objets source vers un objet cible. Il renverra l'objet cible.

var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };

var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1);  // { a: 1, b: 2, c: 3 }, target object itself is changed.

19

Pour les objets pas trop compliqués, vous pouvez utiliser JSON :

var obj1 = { food: 'pizza', car: 'ford' }
var obj2 = { animal: 'dog', car: 'chevy'}
var objMerge;

objMerge = JSON.stringify(obj1) + JSON.stringify(obj2);

// {"food": "pizza","car":"ford"}{"animal":"dog","car":"chevy"}

objMerge = objMerge.replace(/\}\{/, ","); //  \_ replace with comma for valid JSON

objMerge = JSON.parse(objMerge); // { food: 'pizza', animal: 'dog', car: 'chevy'}
// Of same keys in both objects, the last object's value is retained_/

Rappelez-vous que dans cet exemple, "} {" ne doit pas apparaître dans une chaîne!


4
var so1 = JSON.stringify(obj1); var so2 = JSON.stringify(obj1); objMerge = so1.substr(0, so1.length-1)+","+so2.substr(1); console.info(objMerge);
Quamis

function merge(obj1, obj2) { return JSON.parse((JSON.stringify(obj1) + JSON.stringify(obj2)) .replace(/\}\{/g, ',').replace(/,\}/g, '}').replace(/\{,/g, '{')); }
Alex Ivasyuv

ou en ligne individuelle:objMerge = JSON.parse((JSON.stringify(obj1) + JSON.stringify(obj2)).replace(/\}\{/, ", "));
Rabbi Shuki Gur

1
@Charles, désolé mais comment "cette méthode est-elle très dangereuse" mais aussi amusante - c'est la plus sûre possible. D'abord, elle utilise JSON pour faire le gros du travail de lecture du contenu de l'objet, d'autre part, elle utilise l'objet JSON qui est certainement le plus sûr. optimisé pour la récupération de JSO à partir d'un littéral de chaîne d'objet compatible JSON. Et est également rétrocompatible avec IE8 qui est le premier navigateur à implémenter sa norme. Cela me fait juste me demander "pourquoi oh pourquoi avez-vous dû dire une chose aussi stupide et vous en tirer"?
Bekim Bacaj

Je crois que les fonctions de l'objet se décomposeront ici car stringify ne peut pas être utilisé, mais pouvons-nous clarifier d'autres inconvénients?
yeahdixon

18

Il y a une bibliothèque appelée deepmergesur GitHub : Cela semble obtenir une certaine traction. Il s'agit d'un logiciel autonome, disponible via les gestionnaires de packages npm et bower.

Je serais enclin à utiliser ou à améliorer cela au lieu de copier-coller le code des réponses.


17

La meilleure façon de procéder consiste à ajouter une propriété appropriée non énumérable à l'aide d'Object.defineProperty.

De cette façon, vous pourrez toujours parcourir les propriétés de vos objets sans avoir la "nouvelle extension" que vous obtiendriez si vous deviez créer la propriété avec Object.prototype.extend.

Espérons que cela aide:

Object.defineProperty (Object.prototype, "extend", {
    énumérable: faux,
    valeur: fonction (de) {
        var props = Object.getOwnPropertyNames (from);
        var dest = this;
        props.forEach (fonction (nom) {
            if (nom dans dest) {
                var destination = Object.getOwnPropertyDescriptor (de, nom);
                Object.defineProperty (dest, nom, destination);
            }
        });
        retourner ceci;
    }
});

Une fois que cela fonctionne, vous pouvez faire:

var obj = {
    nom: «pile»,
    finition: «débordement»
}
remplacement var = {
    nom: «stock»
};

obj.extend (remplacement);

Je viens d'écrire un article de blog à ce sujet ici: http://onemoredigit.com/post/1527191998/extending-objects-in-node-js


Attendez une seconde --- le code ne fonctionne pas! :( inséré dans jsperf pour le comparer à d'autres algorithmes de fusion, et le code échoue - l'extension de fonction n'existe pas
Jens Roland

16

Vous pouvez simplement utiliser jQuery extend

var obj1 = { val1: false, limit: 5, name: "foo" };
var obj2 = { val2: true, name: "bar" };

jQuery.extend(obj1, obj2);

obj1Contient maintenant toutes les valeurs de obj1etobj2


2
obj1 contiendra toutes les valeurs, pas obj2. Vous étendez le premier objet. jQuery.extend( target [, object1 ] [, objectN ] )
ruuter

5
Cette question ne concerne pas jQuery. JavaScript! = JQuery
Jorge Riv

1
J'ai cherché sur JavaScript, mais c'est une approche plus simple
Ehsan

13

Le prototype a ceci:

Object.extend = function(destination,source) {
    for (var property in source)
        destination[property] = source[property];
    return destination;
}

obj1.extend(obj2) fera ce que vous voulez.


6
Vous vouliez dire Object.prototype.extend? Dans tous les cas, ce n'est pas une bonne idée d'étendre Object.prototype. -1.
SolutionYogi

À bien y penser, il devrait probablement l'être Object.prototypeet non Object... Bonne idée ou non, c'est ce que fait le prototype.jsframework, et donc si vous l'utilisez ou un autre framework qui en dépend, Object.prototypesera déjà étendu avec extend.
éphémère

2
Je ne veux pas vous harceler mais dire que ça va parce que prototype.js le fait est un mauvais argument. Prototype.js est populaire, mais cela ne signifie pas qu'ils sont le modèle sur la façon de faire JavaScript. Consultez cette revue détaillée de la bibliothèque de prototypes par un pro de JS. dhtmlkitchen.com/?category=/JavaScript/&date=2008/06/17/…
SolutionYogi

7
Qui s'en soucie si c'est bien ou mal? Si cela fonctionne, cela fonctionne. Attendre la solution parfaite est génial, sauf lorsque vous essayez de conserver un contrat, de gagner de nouveaux clients ou de respecter les délais. Donnez à tous ceux qui sont passionnés par la programmation de l'argent gratuit et ils finiront par tout faire "correctement".
David

1
Où avez-vous trouvé tous les gars Object.prototype? Object.extendn'interfère pas avec vos objets, il attache simplement une fonction à un Objectobjet. Et obj1.extend(obj2)n'est pas la bonne façon de tirer la fonction, utilisez Object.extend(obj1, obj2)insted
artnikpro

13

** La fusion d'objets est simple à utiliser Object.assignou à l' ...opérateur d' étalement **

var obj1 = { food: 'pizza', car: 'ford' }
var obj2 = { animal: 'dog', car: 'BMW' }
var obj3 = {a: "A"}


var mergedObj = Object.assign(obj1,obj2,obj3)
 // or using the Spread operator (...)
var mergedObj = {...obj1,...obj2,...obj3}

console.log(mergedObj);

Les objets sont fusionnés de droite à gauche, cela signifie que les objets qui ont des propriétés identiques à celles des objets à droite seront remplacés.

Dans cet exemple, obj2.carremplaceobj1.car



10

J'ai étendu la méthode de David Coallier:

  • Ajout de la possibilité de fusionner plusieurs objets
  • Prend en charge les objets profonds
  • remplacer le paramètre (qui est détecté si le dernier paramètre est un booléen)

Si la substitution est fausse, aucune propriété n'est remplacée mais de nouvelles propriétés seront ajoutées.

Utilisation: obj.merge (fusionne ... [, override]);

Voici mon code:

Object.defineProperty(Object.prototype, "merge", {
    enumerable: false,
    value: function () {
        var override = true,
            dest = this,
            len = arguments.length,
            props, merge, i, from;

        if (typeof(arguments[arguments.length - 1]) === "boolean") {
            override = arguments[arguments.length - 1];
            len = arguments.length - 1;
        }

        for (i = 0; i < len; i++) {
            from = arguments[i];
            if (from != null) {
                Object.getOwnPropertyNames(from).forEach(function (name) {
                    var descriptor;

                    // nesting
                    if ((typeof(dest[name]) === "object" || typeof(dest[name]) === "undefined")
                            && typeof(from[name]) === "object") {

                        // ensure proper types (Array rsp Object)
                        if (typeof(dest[name]) === "undefined") {
                            dest[name] = Array.isArray(from[name]) ? [] : {};
                        }
                        if (override) {
                            if (!Array.isArray(dest[name]) && Array.isArray(from[name])) {
                                dest[name] = [];
                            }
                            else if (Array.isArray(dest[name]) && !Array.isArray(from[name])) {
                                dest[name] = {};
                            }
                        }
                        dest[name].merge(from[name], override);
                    } 

                    // flat properties
                    else if ((name in dest && override) || !(name in dest)) {
                        descriptor = Object.getOwnPropertyDescriptor(from, name);
                        if (descriptor.configurable) {
                            Object.defineProperty(dest, name, descriptor);
                        }
                    }
                });
            }
        }
        return this;
    }
});

Exemples et cas de test:

function clone (obj) {
    return JSON.parse(JSON.stringify(obj));
}
var obj = {
    name : "trick",
    value : "value"
};

var mergeObj = {
    name : "truck",
    value2 : "value2"
};

var mergeObj2 = {
    name : "track",
    value : "mergeObj2",
    value2 : "value2-mergeObj2",
    value3 : "value3"
};

assertTrue("Standard", clone(obj).merge(mergeObj).equals({
    name : "truck",
    value : "value",
    value2 : "value2"
}));

assertTrue("Standard no Override", clone(obj).merge(mergeObj, false).equals({
    name : "trick",
    value : "value",
    value2 : "value2"
}));

assertTrue("Multiple", clone(obj).merge(mergeObj, mergeObj2).equals({
    name : "track",
    value : "mergeObj2",
    value2 : "value2-mergeObj2",
    value3 : "value3"
}));

assertTrue("Multiple no Override", clone(obj).merge(mergeObj, mergeObj2, false).equals({
    name : "trick",
    value : "value",
    value2 : "value2",
    value3 : "value3"
}));

var deep = {
    first : {
        name : "trick",
        val : "value"
    },
    second : {
        foo : "bar"
    }
};

var deepMerge = {
    first : {
        name : "track",
        anotherVal : "wohoo"
    },
    second : {
        foo : "baz",
        bar : "bam"
    },
    v : "on first layer"
};

assertTrue("Deep merges", clone(deep).merge(deepMerge).equals({
    first : {
        name : "track",
        val : "value",
        anotherVal : "wohoo"
    },
    second : {
        foo : "baz",
        bar : "bam"
    },
    v : "on first layer"
}));

assertTrue("Deep merges no override", clone(deep).merge(deepMerge, false).equals({
    first : {
        name : "trick",
        val : "value",
        anotherVal : "wohoo"
    },
    second : {
        foo : "bar",
        bar : "bam"
    },
    v : "on first layer"
}));

var obj1 = {a: 1, b: "hello"};
obj1.merge({c: 3});
assertTrue(obj1.equals({a: 1, b: "hello", c: 3}));

obj1.merge({a: 2, b: "mom", d: "new property"}, false);
assertTrue(obj1.equals({a: 1, b: "hello", c: 3, d: "new property"}));

var obj2 = {};
obj2.merge({a: 1}, {b: 2}, {a: 3});
assertTrue(obj2.equals({a: 3, b: 2}));

var a = [];
var b = [1, [2, 3], 4];
a.merge(b);
assertEquals(1, a[0]);
assertEquals([2, 3], a[1]);
assertEquals(4, a[2]);


var o1 = {};
var o2 = {a: 1, b: {c: 2}};
var o3 = {d: 3};
o1.merge(o2, o3);
assertTrue(o1.equals({a: 1, b: {c: 2}, d: 3}));
o1.b.c = 99;
assertTrue(o2.equals({a: 1, b: {c: 2}}));

// checking types with arrays and objects
var bo;
a = [];
bo = [1, {0:2, 1:3}, 4];
b = [1, [2, 3], 4];

a.merge(b);
assertTrue("Array stays Array?", Array.isArray(a[1]));

a = [];
a.merge(bo);
assertTrue("Object stays Object?", !Array.isArray(a[1]));

a = [];
a.merge(b);
a.merge(bo);
assertTrue("Object overrides Array", !Array.isArray(a[1]));

a = [];
a.merge(b);
a.merge(bo, false);
assertTrue("Object does not override Array", Array.isArray(a[1]));

a = [];
a.merge(bo);
a.merge(b);
assertTrue("Array overrides Object", Array.isArray(a[1]));

a = [];
a.merge(bo);
a.merge(b, false);
assertTrue("Array does not override Object", !Array.isArray(a[1]));

Ma méthode d'égalité peut être trouvée ici: Comparaison d'objets en JavaScript



9

Dans Ext JS 4, cela peut être fait comme suit:

var mergedObject = Ext.Object.merge(object1, object2)

// Or shorter:
var mergedObject2 = Ext.merge(object1, object2)

Voir merge (object): Object .


1
Méfiez-vous du bogue EXTJS-9173 dans EXT.Object.merge pour l'instant après +2 ans non encore résolu
Chris

9

Wow .. c'est le premier article StackOverflow que j'ai vu avec plusieurs pages. Toutes mes excuses pour avoir ajouté une autre "réponse"

Cette méthode s'applique à ES5 et aux versions antérieures - il existe de nombreuses autres réponses concernant ES6.

Je n'ai vu aucun objet "profond" fusionner en utilisant la argumentspropriété. Voici ma réponse - compacte et récursive , permettant de passer des arguments d'objet illimités:

function extend() {
    for (var o = {}, i = 0; i < arguments.length; i++) {
        // if (arguments[i].constructor !== Object) continue;
        for (var k in arguments[i]) {
            if (arguments[i].hasOwnProperty(k)) {
                o[k] = arguments[i][k].constructor === Object ? extend(o[k] || {}, arguments[i][k]) : arguments[i][k];
            }
        }
    }
    return o;
}

La partie commentée est facultative. Elle sautera simplement les arguments passés qui ne sont pas des objets (empêchant les erreurs).

Exemple:

extend({
    api: 1,
    params: {
        query: 'hello'
    }
}, {
    params: {
        query: 'there'
    }
});

// outputs {api: 1, params: {query: 'there'}}

Cette réponse n'est maintenant qu'une goutte dans l'océan ...


La réponse la plus courte qui fonctionne très bien! Il mérite plus de votes positifs!
Jens Törnell

8
var obj1 = { food: 'pizza', car: 'ford' }
var obj2 = { animal: 'dog' }

// result
result: {food: "pizza", car: "ford", animal: "dog"}

Utilisation de jQuery.extend () - Lien

// Merge obj1 & obj2 to result
var result1 = $.extend( {}, obj1, obj2 );

Utilisation de _.merge () - Lien

// Merge obj1 & obj2 to result
var result2 = _.merge( {}, obj1, obj2 );

Utilisation de _.extend () - Lien

// Merge obj1 & obj2 to result
var result3 = _.extend( {}, obj1, obj2 );

Utilisation d'Object.assign () ECMAScript 2015 (ES6) - Lien

// Merge obj1 & obj2 to result
var result4 = Object.assign( {}, obj1, obj2 );

Sortie de tous

obj1: { animal: 'dog' }
obj2: { food: 'pizza', car: 'ford' }
result1: {food: "pizza", car: "ford", animal: "dog"}
result2: {food: "pizza", car: "ford", animal: "dog"}
result3: {food: "pizza", car: "ford", animal: "dog"}
result4: {food: "pizza", car: "ford", animal: "dog"}

7

Basé sur la réponse de Markus et vsync , il s'agit d'une version étendue. La fonction prend n'importe quel nombre d'arguments. Il peut être utilisé pour définir des propriétés sur les nœuds DOM et faire des copies complètes des valeurs. Cependant, le premier argument est donné par référence.

Pour détecter un nœud DOM, la fonction isDOMNode () est utilisée (voir la question Stack Overflow JavaScript isDOM - Comment vérifier si un objet JavaScript est un objet DOM? )

Il a été testé dans Opera 11, Firefox 6, Internet Explorer 8 et Google Chrome 16.

Code

function mergeRecursive() {

  // _mergeRecursive does the actual job with two arguments.
  var _mergeRecursive = function (dst, src) {
    if (isDOMNode(src) || typeof src !== 'object' || src === null) {
      return dst;
    }

    for (var p in src) {
      if (!src.hasOwnProperty(p))
        continue;
      if (src[p] === undefined)
        continue;
      if ( typeof src[p] !== 'object' || src[p] === null) {
        dst[p] = src[p];
      } else if (typeof dst[p]!=='object' || dst[p] === null) {
        dst[p] = _mergeRecursive(src[p].constructor===Array ? [] : {}, src[p]);
      } else {
        _mergeRecursive(dst[p], src[p]);
      }
    }
    return dst;
  }

  // Loop through arguments and merge them into the first argument.
  var out = arguments[0];
  if (typeof out !== 'object' || out === null)
    return out;
  for (var i = 1, il = arguments.length; i < il; i++) {
    _mergeRecursive(out, arguments[i]);
  }
  return out;
}

Quelques exemples

Définir innerHTML et le style d'un élément HTML

mergeRecursive(
  document.getElementById('mydiv'),
  {style: {border: '5px solid green', color: 'red'}},
  {innerHTML: 'Hello world!'});

Fusionner des tableaux et des objets. Notez que non défini peut être utilisé pour conserver les valeurs dans le tableau / objet de gauche.

o = mergeRecursive({a:'a'}, [1,2,3], [undefined, null, [30,31]], {a:undefined, b:'b'});
// o = {0:1, 1:null, 2:[30,31], a:'a', b:'b'}

Tout argument ne correspondant pas à un objet JavaScript (y compris null) sera ignoré. À l'exception du premier argument, les nœuds DOM sont également ignorés. Attention, c'est-à-dire que les chaînes, créées comme de nouvelles String () sont en fait des objets.

o = mergeRecursive({a:'a'}, 1, true, null, undefined, [1,2,3], 'bc', new String('de'));
// o = {0:'d', 1:'e', 2:3, a:'a'}

Si vous voulez fusionner deux objets dans un nouveau (sans affecter aucun des deux), fournissez {} comme premier argument

var a={}, b={b:'abc'}, c={c:'cde'}, o;
o = mergeRecursive(a, b, c);
// o===a is true, o===b is false, o===c is false

Modifier (par ReaperSoon):

Pour fusionner également des tableaux

function mergeRecursive(obj1, obj2) {
  if (Array.isArray(obj2)) { return obj1.concat(obj2); }
  for (var p in obj2) {
    try {
      // Property in destination object set; update its value.
      if ( obj2[p].constructor==Object ) {
        obj1[p] = mergeRecursive(obj1[p], obj2[p]);
      } else if (Array.isArray(obj2[p])) {
        obj1[p] = obj1[p].concat(obj2[p]);
      } else {
        obj1[p] = obj2[p];
      }
    } catch(e) {
      // Property in destination object not set; create it and set its value.
      obj1[p] = obj2[p];
    }
  }
  return obj1;
}


5

Vous devez utiliser les valeurs par défaut de lodash

_.defaultsDeep({ 'user': { 'name': 'barney' } }, { 'user': { 'name': 'fred', 'age': 36 } });
// → { 'user': { 'name': 'barney', 'age': 36 } }

5

Avec Underscore.js , pour fusionner un tableau d'objets, procédez comme suit:

var arrayOfObjects = [ {a:1}, {b:2, c:3}, {d:4} ];
_(arrayOfObjects).reduce(function(memo, o) { return _(memo).extend(o); });

Il en résulte:

Object {a: 1, b: 2, c: 3, d: 4}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.