Les deux Object.assign et la propagation objet que de faire une fusion peu profonde.
Un exemple du problème:
// No object nesting
const x = { a: 1 }
const y = { b: 1 }
const z = { ...x, ...y } // { a: 1, b: 1 }
La sortie est ce que vous attendez. Cependant, si j'essaye ceci:
// Object nesting
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = { ...x, ...y } // { a: { b: 1 } }
Au lieu de
{ a: { a: 1, b: 1 } }
vous obtenez
{ a: { b: 1 } }
x est complètement écrasé car la syntaxe de propagation ne va que d'un niveau. C'est la même chose avec Object.assign()
.
Y a-t-il un moyen de faire cela?
Réponses:
Non.
la source
Je sais que c'est un peu un vieux problème mais la solution la plus simple dans ES2015 / ES6 que j'ai pu trouver était en fait assez simple, en utilisant Object.assign (),
Espérons que cela aide:
Exemple d'utilisation:
Vous en trouverez une version immuable dans la réponse ci-dessous.
Notez que cela conduira à une récursion infinie sur les références circulaires. Il existe d'excellentes réponses ici sur la façon de détecter les références circulaires si vous pensez que vous seriez confronté à ce problème.
la source
item !== null
ne devrait pas être nécessaire à l'intérieurisObject
, car laitem
véracité est déjà vérifiée au début de la conditionObject.assign(target, { [key]: {} })
si cela pouvait être simplementtarget[key] = {}
?target[key] = source[key]
au lieu deObject.assign(target, { [key]: source[key] });
target
. Par exemple,mergeDeep({a: 3}, {a: {b: 4}})
se traduira par unNumber
objet augmenté , ce qui n'est clairement pas souhaité. En outre,isObject
n'accepte pas les tableaux, mais accepte tout autre type d'objet natif, tel queDate
, qui ne doit pas être copié en profondeur.Vous pouvez utiliser la fusion Lodash :
la source
{ 'a': [{ 'b': 2 }, { 'c': 3 }, { 'd': 4 }, { 'e': 5 }] }
?{ 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
est correct, car nous fusionnons les éléments d'un tableau. L'élément0
deobject.a
est{b: 2}
, l'élément0
deother.a
est{c: 3}
. Lorsque ces deux sont fusionnés car ils ont le même index de tableau, le résultat est{ 'b': 2, 'c': 3 }
, qui est l'élément0
dans le nouvel objet.Le problème n'est pas trivial lorsqu'il s'agit d'héberger des objets ou tout type d'objet plus complexe qu'un sac de valeurs
Une autre chose à garder à l'esprit: les graphes d'objets qui contiennent des cycles. Il n'est généralement pas difficile à gérer - conservez simplement un
Set
des objets source déjà visités - mais souvent oublié.Vous devriez probablement écrire une fonction de fusion profonde qui n'attend que des valeurs primitives et des objets simples - tout au plus les types que l' algorithme de clone structuré peut gérer - comme sources de fusion. Lancer s'il rencontre quelque chose qu'il ne peut pas gérer ou simplement attribuer par référence au lieu d'une fusion en profondeur.
En d'autres termes, il n'y a pas d'algorithme unique, vous devez soit lancer le vôtre, soit rechercher une méthode de bibliothèque qui couvre vos cas d'utilisation.
la source
Voici une version immuable (ne modifie pas les entrées) de la réponse de @ Salakar. Utile si vous faites des trucs de type programmation fonctionnelle.
la source
key
comme nom de propriété, le second fera "clé" le nom de la propriété. Voir: es6-features.org/#ComputedPropertyNamesisObject
vous n'avez pas besoin de vérifier&& item !== null
à la fin, parce que la ligne commence avecitem &&
, non?mergedDeep
la sortie de (je pense). Par exemple,const target = { a: 1 }; const source = { b: { c: 2 } }; const merged = mergeDeep(target, source);
merged.b.c; // 2
source.b.c = 3;
merged.b.c; // 3
est-ce un problème? Il ne mute pas les entrées, mais toute mutation future des entrées pourrait muter la sortie, et vice versa avec des mutations pour sortir les entrées mutantes. Pour ce que ça vaut, cependant, ramdaR.merge()
a le même comportement.Étant donné que ce problème est toujours actif, voici une autre approche:
la source
prev[key] = pVal.concat(...oVal);
àprev[key] = [...pVal, ...oVal].filter((element, index, array) => array.indexOf(element) === index);
reduce
n'est pas le cas.Je sais qu'il y a déjà beaucoup de réponses et autant de commentaires soutenant qu'ils ne fonctionneront pas. Le seul consensus est que c'est tellement compliqué que personne n'a fait de norme pour cela . Cependant, la plupart des réponses acceptées dans SO exposent des «astuces simples» qui sont largement utilisées. Donc, pour nous tous comme moi qui ne sommes pas des experts mais qui veulent écrire du code plus sûr en saisissant un peu plus la complexité de javascript, je vais essayer de faire la lumière.
Avant de nous salir les mains, permettez-moi de clarifier 2 points:
Object.assign
ça.Réponses avec
for..in
ouObject.keys
trompeusesFaire une copie en profondeur semble une pratique tellement basique et courante que nous nous attendons à trouver un one-liner ou, au moins, une victoire rapide via une simple récursivité. Nous ne nous attendons pas à ce que nous ayons besoin d'une bibliothèque ou d'écrire une fonction personnalisée de 100 lignes.
Quand j'ai lu la réponse de Salakar pour la première fois , j'ai vraiment pensé que je pouvais faire mieux et plus simplement (vous pouvez le comparer avec
Object.assign
onx={a:1}, y={a:{b:1}}
). Puis j'ai lu la réponse du 8447 et j'ai pensé ... qu'il n'y a pas de moyen de s'échapper si facilement, l'amélioration des réponses déjà données ne nous mènera pas loin.Laissons la copie profonde et récursive de côté un instant. Considérez simplement comment (à tort) les gens analysent les propriétés pour copier un objet très simple.
Object.keys
omettra ses propres propriétés non énumérables, ses propres propriétés à clé de symbole et toutes les propriétés du prototype. Cela peut être bien si vos objets n'en ont pas. Mais gardez à l'esprit qu'ilObject.assign
gère ses propres propriétés énumérables à clé. Votre copie personnalisée a donc perdu son éclat.for..in
fournira les propriétés de la source, de son prototype et de la chaîne complète de prototypes sans que vous le vouliez (ou que vous le sachiez). Votre cible peut se retrouver avec trop de propriétés, mélangeant des propriétés de prototype et des propriétés propres.Si vous écrivez une fonction d'usage général et vous ne l' utilisez
Object.getOwnPropertyDescriptors
,Object.getOwnPropertyNames
,Object.getOwnPropertySymbols
ouObject.getPrototypeOf
, vous faites sans doute le plus mal.Éléments à considérer avant d'écrire votre fonction
Tout d'abord, assurez-vous de comprendre ce qu'est un objet Javascript. En Javascript, un objet est composé de ses propres propriétés et d'un objet prototype (parent). L'objet prototype est à son tour constitué de ses propres propriétés et d'un objet prototype. Et ainsi de suite, définir une chaîne prototype.
Une propriété est une paire de clé (
string
ousymbol
) et de descripteur (value
ouget
/set
accesseur, et des attributs commeenumerable
).Enfin, il existe de nombreux types d'objets . Vous souhaiterez peut-être gérer différemment un objet Object à partir d'un objet Date ou d'un objet Function.
Donc, en écrivant votre copie complète, vous devez répondre au moins à ces questions:
Pour mon exemple, je considère que seuls les
object Object
s sont profonds , car d'autres objets créés par d'autres constructeurs peuvent ne pas convenir à un examen approfondi. Personnalisé à partir de ce SO .Et j'ai fait un
options
objet pour choisir quoi copier (à des fins de démonstration).Fonction proposée
Vous pouvez le tester dans ce plunker .
Cela peut être utilisé comme ceci:
la source
J'utilise lodash:
la source
_cloneDeep(value1).merge(value2)
Voici l'implémentation TypeScript:
Et tests unitaires:
la source
Voici une autre solution ES6, qui fonctionne avec des objets et des tableaux.
la source
Le package deepmerge npm semble être la bibliothèque la plus utilisée pour résoudre ce problème: https://www.npmjs.com/package/deepmerge
la source
Je voudrais présenter une alternative ES5 assez simple. La fonction obtient 2 paramètres -
target
etsource
cela doit être de type "objet".Target
sera l'objet résultant.Target
conserve toutes ses propriétés d'origine mais leurs valeurs peuvent cependant être modifiées.cas:
target
n'a pas desource
propriété,target
obtient;target
a unesource
propriété ettarget
&source
ne sont pas les deux objets (3 cas sur 4),target
s'outrepassée la propriété »;target
possède unesource
propriété et que les deux sont des objets / tableaux (1 cas restant), alors la récursivité se produit en fusionnant deux objets (ou concaténation de deux tableaux);tenez également compte des éléments suivants :
Il est prévisible, prend en charge les types primitifs ainsi que les tableaux et les objets. De plus, comme nous pouvons fusionner 2 objets, je pense que nous pouvons fusionner plus de 2 via la fonction de réduction .
jetez un oeil à un exemple (et jouez avec si vous voulez) :
Il existe une limitation - la longueur de la pile d'appels du navigateur. Les navigateurs modernes génèrent une erreur à un niveau de récursion très profond (pensez à des milliers d'appels imbriqués). Vous êtes également libre de traiter des situations comme tableau + objet, etc. comme vous le souhaitez en ajoutant de nouvelles conditions et en vérifiant le type.
la source
Si vous utilisez ImmutableJS, vous pouvez utiliser
mergeDeep
:la source
Si les bibliothèques npm peuvent être utilisées comme solution, la fusion d'objets avancée de la vôtre permet vraiment de fusionner les objets en profondeur et de personnaliser / remplacer chaque action de fusion à l'aide d'une fonction de rappel familière. L'idée principale est plus qu'une simple fusion profonde - que se passe-t-il avec la valeur lorsque deux clés sont identiques ? Cette bibliothèque s'en charge - lorsque deux touches s'affrontent,
object-merge-advanced
pèse les types, dans le but de conserver autant de données que possible après la fusion:La clé du premier argument d'entrée est marquée # 1, celle du deuxième argument - # 2. En fonction de chaque type, un est choisi pour la valeur de la clé de résultat. Dans le diagramme, "un objet" signifie un objet simple (pas un tableau, etc.).
Lorsque les touches ne s'affrontent pas, elles entrent toutes le résultat.
À partir de votre exemple d'extrait, si vous avez
object-merge-advanced
fusionné votre extrait de code:Son algorithme traverse récursivement toutes les clés d'objet d'entrée, compare et construit et renvoie le nouveau résultat fusionné.
la source
La fonction suivante crée une copie complète des objets, elle couvre la copie de primitives, de tableaux et d'objets
la source
Une solution simple avec ES5 (écraser la valeur existante):
la source
La plupart des exemples ici semblent trop complexes, j'en utilise un dans TypeScript que j'ai créé, je pense qu'il devrait couvrir la plupart des cas (je gère les tableaux comme des données normales, je les remplace simplement).
Même chose en clair JS, juste au cas où:
Voici mes cas de test pour montrer comment vous pouvez l'utiliser
Veuillez me faire savoir si vous pensez que je manque certaines fonctionnalités.
la source
Si vous voulez avoir une seule ligne sans avoir besoin d'une immense bibliothèque comme lodash, je vous suggère d'utiliser deepmerge . (
npm install deepmerge
)Ensuite, vous pouvez faire
obtenir
La bonne chose est qu'il est livré avec des typages pour TypeScript tout de suite. Il permet également de fusionner des tableaux . C'est une vraie solution polyvalente.
la source
Nous pouvons utiliser $ .extend (true, object1, object2) pour une fusion en profondeur. La valeur true indique la fusion récursive de deux objets, en modifiant le premier.
$ extend (vrai, cible, objet)
la source
jQuery.isPlainObject()
. Cela expose la complexité de déterminer si quelque chose est un objet simple, ce que la plupart des réponses ici manquent de loin. Devinez dans quelle langue jQuery est écrit?Voici une solution simple et directe qui fonctionne comme de la
Object.assign
profondeur, et fonctionne pour un tableau, sans aucune modificationExemple
la source
J'avais ce problème lors du chargement d'un état redux mis en cache. Si je charge simplement l'état mis en cache, je rencontrerais des erreurs pour la nouvelle version de l'application avec une structure d'état mise à jour.
Il a déjà été mentionné que lodash offre la
merge
fonction que j'ai utilisée:la source
De nombreuses réponses utilisent des dizaines de lignes de code ou nécessitent l'ajout d'une nouvelle bibliothèque au projet, mais si vous utilisez la récursivité, il ne s'agit que de 4 lignes de code.
Gestion des tableaux: la version ci-dessus remplace les anciennes valeurs de tableau par de nouvelles. Si vous voulez qu'il conserve les anciennes valeurs du tableau et en ajoute de nouvelles, ajoutez simplement un
else if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key])
bloc au-dessus duelse
statament et tout est prêt.la source
Voici un autre que je viens d'écrire qui prend en charge les tableaux. Il les concourt.
la source
Utilisez cette fonction:
la source
Ramda qui est une belle bibliothèque de fonctions javascript a mergeDeepLeft et mergeDeepRight. Tout cela fonctionne assez bien pour ce problème. Veuillez consulter la documentation ici: https://ramdajs.com/docs/#mergeDeepLeft
Pour l'exemple spécifique en question, nous pouvons utiliser:
la source
Test de l'unité:
la source
J'ai trouvé seulement une solution en 2 lignes pour obtenir une fusion profonde en javascript. Faites-moi savoir comment cela fonctionne pour vous.
L'objet temporaire affichera {a: {b: 'd', e: 'f', x: 'y'}}
la source
merge({x:{y:{z:1}}}, {x:{y:{w:2}}})
. Il échouera également à mettre à jour les valeurs existantes dans obj1 si obj2 les a aussi, par exemple avecmerge({x:{y:1}}, {x:{y:2}})
.Parfois, vous n'avez pas besoin d'une fusion profonde, même si vous le pensez. Par exemple, si vous avez une configuration par défaut avec des objets imbriqués et que vous souhaitez l'étendre profondément avec votre propre configuration, vous pouvez créer une classe pour cela. Le concept est très simple:
Vous pouvez le convertir en fonction (pas en constructeur).
la source
Il s'agit d'une fusion en profondeur bon marché qui utilise le moins de code possible. Chaque source écrase la propriété précédente lorsqu'elle existe.
la source
J'utilise la fonction courte suivante pour fusionner des objets en profondeur.
Ça marche bien pour moi.
L'auteur explique complètement comment cela fonctionne ici.
la source