Wie kann man tief verschmelzen anstatt flach verschmelzen?

337

Sowohl Object.assign als auch Object Spread führen nur eine flache Zusammenführung durch.

Ein Beispiel für das Problem:

// No object nesting
const x = { a: 1 }
const y = { b: 1 }
const z = { ...x, ...y } // { a: 1, b: 1 }

Die Ausgabe entspricht Ihren Erwartungen. Wenn ich dies jedoch versuche:

// Object nesting
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = { ...x, ...y } // { a: { b: 1 } }

Anstatt

{ a: { a: 1, b: 1 } }

du erhältst

{ a: { b: 1 } }

x wird vollständig überschrieben, da die Spread-Syntax nur eine Ebene tief geht. Dies ist das gleiche mit Object.assign().

Gibt es eine Möglichkeit, dies zu tun?

Mike
quelle
Ist Deep Merging dasselbe wie das Kopieren von Eigenschaften von einem Objekt auf ein anderes?
2
Nein, da Objekteigenschaften nicht überschrieben werden sollten, sollte jedes untergeordnete Objekt auf dem Ziel mit demselben untergeordneten Objekt zusammengeführt werden, sofern es bereits vorhanden ist.
Mike
ES6 ist fertiggestellt und es werden keine neuen Funktionen mehr hinzugefügt, AFAIK.
Kangax
1
@Oriol erfordert jQuery obwohl ...
m0meni

Antworten:

330

Weiß jemand, ob in der ES6 / ES7-Spezifikation eine tiefe Verschmelzung vorhanden ist?

Nein, tut es nicht.


quelle
21
Bitte überprüfen Sie den Bearbeitungsverlauf. Als ich dies beantwortete, lautete die Frage: Weiß jemand, ob in der ES6 / ES7-Spezifikation eine tiefe Verschmelzung vorhanden ist? .
37
Diese Antwort gilt nicht mehr für diese Frage - sie sollte aktualisiert oder gelöscht werden
DonVaughn
13
Die Frage sollte nicht in diesem Maße bearbeitet worden sein. Änderungen dienen der Verdeutlichung. Eine neue Frage sollte gestellt worden sein.
CJ Thompson
170

Ich weiß, dass dies ein altes Problem ist, aber die einfachste Lösung in ES2015 / ES6, die ich finden konnte, war mit Object.assign () recht einfach.

Hoffentlich hilft das:

/**
 * Simple object check.
 * @param item
 * @returns {boolean}
 */
export function isObject(item) {
  return (item && typeof item === 'object' && !Array.isArray(item));
}

/**
 * Deep merge two objects.
 * @param target
 * @param ...sources
 */
export function mergeDeep(target, ...sources) {
  if (!sources.length) return target;
  const source = sources.shift();

  if (isObject(target) && isObject(source)) {
    for (const key in source) {
      if (isObject(source[key])) {
        if (!target[key]) Object.assign(target, { [key]: {} });
        mergeDeep(target[key], source[key]);
      } else {
        Object.assign(target, { [key]: source[key] });
      }
    }
  }

  return mergeDeep(target, ...sources);
}

Anwendungsbeispiel:

mergeDeep(this, { a: { b: { c: 123 } } });
// or
const merged = mergeDeep({a: 1}, { b : { c: { d: { e: 12345}}}});  
console.dir(merged); // { a: 1, b: { c: { d: [Object] } } }

Eine unveränderliche Version davon finden Sie in der Antwort unten.

Beachten Sie, dass dies zu einer unendlichen Rekursion von Zirkelverweisen führt. Hier finden Sie einige gute Antworten zum Erkennen von Zirkelverweisen, wenn Sie glauben, dass Sie mit diesem Problem konfrontiert sind.

Salakar
quelle
1
Wenn Ihr Objektdiagramm Zyklen enthält, die zu einer unendlichen Rekursion führen
the8472
2
Warum das schreiben: Object.assign(target, { [key]: {} })wenn es einfach sein könnte target[key] = {}?
Jürg Lehni
1
... und target[key] = source[key]stattObject.assign(target, { [key]: source[key] });
Jürg Lehni
3
Dies unterstützt keine nicht einfachen Objekte in target. Zum Beispiel mergeDeep({a: 3}, {a: {b: 4}})wird in einem Augmented führt NumberObjekt, das eindeutig nicht erwünscht ist. Auch isObjectnicht - Arrays akzeptiert, akzeptiert aber anderen nativen Objekttyp, wie zum Beispiel Date, die nicht tief kopiert werden.
Riv
2
Es funktioniert nicht mit Arrays, wie ich verstehe?
Vedmant
119

Sie können Lodash Merge verwenden :

var object = {
  'a': [{ 'b': 2 }, { 'd': 4 }]
};

var other = {
  'a': [{ 'c': 3 }, { 'e': 5 }]
};

_.merge(object, other);
// => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
AndrewHenderson
quelle
6
Hey Leute, das ist die einfachste und schönste Lösung. Lodash ist fantastisch, sie sollten es als Kernobjekt enthalten
Nurbol Alpysbayev
11
Sollte das Ergebnis nicht sein { 'a': [{ 'b': 2 }, { 'c': 3 }, { 'd': 4 }, { 'e': 5 }] }?
J. Hesters
Gute Frage. Dies könnte eine separate Frage oder eine für die Lodash-Betreuer sein.
Andrew Henderson
7
Das Ergebnis { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }ist korrekt, da wir Elemente eines Arrays zusammenführen. Das Element 0von object.aist {b: 2}, das Element 0von other.aist {c: 3}. Wenn diese beiden zusammengeführt werden, weil sie denselben Array-Index haben, ist das Ergebnis { 'b': 2, 'c': 3 }das Element 0im neuen Objekt.
Alexandru Furculita
Ich bevorzuge dieses , es ist 6x kleiner gezippt.
Solo
101

Das Problem ist nicht trivial, wenn es um Host-Objekte oder Objekte jeglicher Art geht, die komplexer sind als eine Tüte mit Werten

  • Rufen Sie einen Getter auf, um einen Wert zu erhalten, oder kopieren Sie ihn über den Eigenschaftsdeskriptor?
  • Was ist, wenn das Zusammenführungsziel einen Setter hat (entweder eigenes Eigentum oder in seiner Prototypenkette)? Betrachten Sie den Wert als bereits vorhanden oder rufen Sie den Setter an, um den aktuellen Wert zu aktualisieren?
  • Rufen Sie Eigenschaftsfunktionen auf oder kopieren Sie sie? Was ist, wenn sie gebundene Funktionen oder Pfeilfunktionen sind, abhängig von etwas in ihrer Bereichskette zum Zeitpunkt ihrer Definition?
  • Was ist, wenn es so etwas wie ein DOM-Knoten ist? Sie möchten es sicherlich nicht als einfaches Objekt behandeln und einfach alle seine Eigenschaften in sich zusammenführen
  • Wie gehe ich mit "einfachen" Strukturen wie Arrays oder Maps oder Sets um? Betrachten Sie sie als bereits vorhanden oder führen Sie sie auch zusammen?
  • Wie gehe ich mit nicht aufzählbaren eigenen Eigenschaften um?
  • Was ist mit neuen Teilbäumen? Einfach durch Referenz oder Deep Clone zuweisen?
  • Wie gehe ich mit gefrorenen / versiegelten / nicht dehnbaren Objekten um?

Noch etwas zu beachten: Objektdiagramme, die Zyklen enthalten. Es ist normalerweise nicht schwer damit umzugehen - behalten Sie einfach ein Setbereits besuchtes Quellobjekt - aber oft vergessen.

Sie sollten wahrscheinlich eine Deep-Merge-Funktion als Merge-Quellen schreiben, die nur primitive Werte und einfache Objekte erwartet - höchstens die Typen, die der strukturierte Klon-Algorithmus verarbeiten kann . Werfen, wenn es auf etwas stößt, das es nicht verarbeiten kann, oder nur durch Referenz zuweisen, anstatt tief zu verschmelzen.

Mit anderen Worten, es gibt keinen einheitlichen Algorithmus. Sie müssen entweder Ihren eigenen Algorithmus erstellen oder nach einer Bibliotheksmethode suchen, die Ihre Anwendungsfälle abdeckt.

the8472
quelle
2
Entschuldigungen für V8-Entwickler, keine sichere Übertragung des "Dokumentstatus" zu implementieren
neaumusic
Sie werfen viele gute Fragen auf, und ich hätte gerne eine Umsetzung Ihrer Empfehlung gesehen. Also habe ich versucht, einen unten zu machen. Könnten Sie bitte einen Blick darauf werfen und einen Kommentar abgeben? stackoverflow.com/a/48579540/8122487
RaphaMex
66

Hier ist eine unveränderliche Version der Antwort von @ Salakar (ändert die Eingaben nicht). Nützlich, wenn Sie funktionale Programmieraufgaben ausführen.

export function isObject(item) {
  return (item && typeof item === 'object' && !Array.isArray(item));
}

export default function mergeDeep(target, source) {
  let output = Object.assign({}, target);
  if (isObject(target) && isObject(source)) {
    Object.keys(source).forEach(key => {
      if (isObject(source[key])) {
        if (!(key in target))
          Object.assign(output, { [key]: source[key] });
        else
          output[key] = mergeDeep(target[key], source[key]);
      } else {
        Object.assign(output, { [key]: source[key] });
      }
    });
  }
  return output;
}
CpILL
quelle
1
@torazaburo siehe vorherigen Beitrag von mir für die isObject-Funktion
Salakar
aktualisiert es. Nach einigen Tests fand ich einen Fehler mit den tief verschachtelten Objekten
CpILL
3
Es ist ein berechneter Eigenschaftsname, der erste verwendet den Wert von keyals Eigenschaftsname, der spätere macht "Schlüssel" zum Eigenschaftsnamen. Siehe: es6-features.org/#ComputedPropertyNames
CpILL
2
in müssen isObjectSie nicht && item !== nullam Ende überprüfen , weil die Zeile beginnt mit item &&, nein?
Ephemer
2
Wenn die Quelle untergeordnete Objekte tiefer als das Ziel verschachtelt hat, verweisen diese Objekte immer noch auf dieselben Werte in mergedDeepder Ausgabe (glaube ich). ZB 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 Ist das ein Problem? Die Eingaben werden nicht mutiert, aber zukünftige Mutationen an den Eingaben können die Ausgabe mutieren und umgekehrt mit Mutationen, um mutierende Eingaben auszugeben. Für das, was es wert ist, hat Ramda R.merge()das gleiche Verhalten.
James Conkling
40

Da dieses Problem noch aktiv ist, ist hier ein anderer Ansatz:

  • ES6 / 2015
  • Unveränderlich (ändert keine Originalobjekte)
  • Behandelt Arrays (verkettet sie)

/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation.
*
* @param {...object} objects - Objects to merge
* @returns {object} New object with merged key/values
*/
function mergeDeep(...objects) {
  const isObject = obj => obj && typeof obj === 'object';
  
  return objects.reduce((prev, obj) => {
    Object.keys(obj).forEach(key => {
      const pVal = prev[key];
      const oVal = obj[key];
      
      if (Array.isArray(pVal) && Array.isArray(oVal)) {
        prev[key] = pVal.concat(...oVal);
      }
      else if (isObject(pVal) && isObject(oVal)) {
        prev[key] = mergeDeep(pVal, oVal);
      }
      else {
        prev[key] = oVal;
      }
    });
    
    return prev;
  }, {});
}

// Test objects
const obj1 = {
  a: 1,
  b: 1, 
  c: { x: 1, y: 1 },
  d: [ 1, 1 ]
}
const obj2 = {
  b: 2, 
  c: { y: 2, z: 2 },
  d: [ 2, 2 ],
  e: 2
}
const obj3 = mergeDeep(obj1, obj2);

// Out
console.log(obj3);

jhildenbiddle
quelle
Das ist nett. Wenn wir jedoch ein Array mit wiederholten Elementen haben, werden diese verkettet (es gibt wiederholte Elemente). Ich habe dies angepasst, um einen Parameter zu übernehmen (Arrays eindeutig: true / false).
Astronaut
1
Um die Arrays einzigartig zu machen, können Sie prev[key] = pVal.concat(...oVal);zuprev[key] = [...pVal, ...oVal].filter((element, index, array) => array.indexOf(element) === index);
Richard Herries
1
So schön und sauber !! Auf jeden Fall die beste Antwort hier!
538ROMEO
Herrlich. Dieser zeigt auch, dass Arrays zusammengeführt werden, was ich gesucht habe.
Tschallacka
Ja, die @ ClLL-Lösung soll unveränderlich sein, verwendet jedoch die tatsächliche Objektveränderlichkeit innerhalb der Funktion, während reduce dies nicht der Fall ist.
Augustin Riedinger
30

Ich weiß, dass es bereits viele Antworten gibt und ebenso viele Kommentare, die argumentieren, dass sie nicht funktionieren werden. Der einzige Konsens ist, dass es so kompliziert ist, dass niemand einen Standard dafür gemacht hat . Die meisten akzeptierten Antworten in SO enthüllen jedoch "einfache Tricks", die weit verbreitet sind. Für uns alle wie mich, die keine Experten sind, aber sichereren Code schreiben möchten, indem wir etwas mehr über die Komplexität von Javascript erfahren, werde ich versuchen, etwas Licht ins Dunkel zu bringen.

Lassen Sie mich 2 Punkte klarstellen, bevor wir uns die Hände schmutzig machen:

  • [HAFTUNGSAUSSCHLUSS] Ich schlage unten eine Funktion vor, die sich mit der Frage befasst, wie wir zum Kopieren tief in Javascript-Objekte eintauchen, und veranschaulicht, was im Allgemeinen zu kurz kommentiert wird. Es ist nicht produktionsbereit. Aus Gründen der Klarheit habe ich andere Überlegungen wie kreisförmige Objekte (Verfolgung durch eine Menge oder widersprüchliche Symboleigenschaft) , das Kopieren von Referenzwerten oder tiefen Klonen , unveränderliche Zielobjekte (wieder tiefe Klone?) Und Fall-zu-Fall-Studien von absichtlich beiseite gelassen Jede Art von Objekten , Eigenschaften über Accessoren abrufen / festlegen ... Außerdem habe ich die Leistung nicht getestet - obwohl dies wichtig ist -, da es auch hier nicht darum geht.
  • Ich verwende das Kopieren oder Zuweisen von Begriffen anstelle des Zusammenführens . Denn meiner Meinung nach ist eine Fusion konservativ und sollte bei Konflikten scheitern. Hier möchten wir, dass bei Konflikten die Quelle das Ziel überschreibt. Wie Object.assigntut.

Antworten mit for..inoder Object.keyssind irreführend

Das Erstellen einer tiefen Kopie scheint so grundlegend und üblich zu sein, dass wir erwarten, einen Einzeiler oder zumindest einen schnellen Gewinn durch einfache Rekursion zu finden. Wir erwarten nicht, dass wir eine Bibliothek benötigen oder eine benutzerdefinierte Funktion mit 100 Zeilen schreiben sollten.

Als ich Salakars Antwort zum ersten Mal las, dachte ich wirklich, ich könnte es besser und einfacher machen (man kann es mit Object.assignon vergleichen x={a:1}, y={a:{b:1}}). Dann las ich die Antwort des 8472 und dachte ... es gibt kein leichtes Entkommen , die Verbesserung bereits gegebener Antworten bringt uns nicht weit.

Lassen wir einen Moment tief kopieren und rekursiv beiseite. Überlegen Sie einfach, wie (fälschlicherweise) Personen Eigenschaften analysieren, um ein sehr einfaches Objekt zu kopieren.

const y = Object.create(
    { proto : 1 },
    { a: { enumerable: true, value: 1},
      [Symbol('b')] : { enumerable: true, value: 1} } )

Object.assign({},y)
> { 'a': 1, Symbol(b): 1 } // All (enumerable) properties are copied

((x,y) => Object.keys(y).reduce((acc,k) => Object.assign(acc, { [k]: y[k] }), x))({},y)
> { 'a': 1 } // Missing a property!

((x,y) => {for (let k in y) x[k]=y[k];return x})({},y)
> { 'a': 1, 'proto': 1 } // Missing a property! Prototype's property is copied too!

Object.keysEs werden eigene nicht aufzählbare Eigenschaften, eigene symbolgesteuerte Eigenschaften und alle Eigenschaften des Prototyps weggelassen. Es kann in Ordnung sein, wenn Ihre Objekte keine davon haben. Beachten Sie jedoch, dass Object.assigndie eigenen aufzählbaren Eigenschaften mit Symbolschlüssel behandelt werden. So hat Ihre benutzerdefinierte Kopie ihre Blüte verloren.

for..inliefert Eigenschaften der Quelle, ihres Prototyps und der gesamten Prototypenkette, ohne dass Sie dies wünschen (oder wissen). Ihr Ziel hat möglicherweise zu viele Eigenschaften, wodurch Prototyp-Eigenschaften und eigene Eigenschaften verwechselt werden.

Wenn Sie eine Mehrzweckfunktion zu schreiben und Sie nicht verwenden Object.getOwnPropertyDescriptors, Object.getOwnPropertyNames, Object.getOwnPropertySymbolsoder Object.getPrototypeOfsind Sie wahrscheinlich etwas falsch gemacht.

Dinge zu beachten, bevor Sie Ihre Funktion schreiben

Stellen Sie zunächst sicher, dass Sie verstehen, was ein Javascript-Objekt ist. In Javascript besteht ein Objekt aus eigenen Eigenschaften und einem (übergeordneten) Prototypobjekt. Das Prototypobjekt wiederum besteht aus eigenen Eigenschaften und einem Prototypobjekt. Und so weiter, Definition einer Prototypkette.

Eine Eigenschaft ist ein Paar aus Schlüssel ( stringoder symbol) und Deskriptor ( valueoder get/ setAccessor und Attributen wie enumerable).

Schließlich gibt es viele Arten von Objekten . Möglicherweise möchten Sie ein Objekt Objekt anders behandeln als ein Objekt Datum oder eine Objektfunktion.

Wenn Sie also Ihre tiefe Kopie schreiben, sollten Sie mindestens die folgenden Fragen beantworten:

  1. Was halte ich für tief (geeignet für rekursives Nachschlagen) oder flach?
  2. Welche Eigenschaften möchte ich kopieren? (Aufzählbar / Nicht aufzählbar, Zeichenfolgenschlüssel / Symbolschlüssel, eigene Eigenschaften / Eigene Eigenschaften des Prototyps, Werte / Deskriptoren ...)

In meinem Beispiel bin ich der Meinung, dass nur die object Objects tief sind , da andere Objekte, die von anderen Konstruktoren erstellt wurden, möglicherweise nicht für eine eingehende Betrachtung geeignet sind. Angepasst von diesem SO .

function toType(a) {
    // Get fine type (object, array, function, null, error, date ...)
    return ({}).toString.call(a).match(/([a-z]+)(:?\])/i)[1];
}

function isDeepObject(obj) {
    return "Object" === toType(obj);
}

Und ich habe ein optionsObjekt erstellt, um auszuwählen, was kopiert werden soll (für Demozwecke).

const options = {nonEnum:true, symbols:true, descriptors: true, proto:true};

Vorgeschlagene Funktion

Sie können es in diesem Plunker testen .

function deepAssign(options) {
    return function deepAssignWithOptions (target, ...sources) {
        sources.forEach( (source) => {

            if (!isDeepObject(source) || !isDeepObject(target))
                return;

            // Copy source's own properties into target's own properties
            function copyProperty(property) {
                const descriptor = Object.getOwnPropertyDescriptor(source, property);
                //default: omit non-enumerable properties
                if (descriptor.enumerable || options.nonEnum) {
                    // Copy in-depth first
                    if (isDeepObject(source[property]) && isDeepObject(target[property]))
                        descriptor.value = deepAssign(options)(target[property], source[property]);
                    //default: omit descriptors
                    if (options.descriptors)
                        Object.defineProperty(target, property, descriptor); // shallow copy descriptor
                    else
                        target[property] = descriptor.value; // shallow copy value only
                }
            }

            // Copy string-keyed properties
            Object.getOwnPropertyNames(source).forEach(copyProperty);

            //default: omit symbol-keyed properties
            if (options.symbols)
                Object.getOwnPropertySymbols(source).forEach(copyProperty);

            //default: omit prototype's own properties
            if (options.proto)
                // Copy souce prototype's own properties into target prototype's own properties
                deepAssign(Object.assign({},options,{proto:false})) (// Prevent deeper copy of the prototype chain
                    Object.getPrototypeOf(target),
                    Object.getPrototypeOf(source)
                );

        });
        return target;
    }
}

Das kann so verwendet werden:

const x = { a: { a: 1 } },
      y = { a: { b: 1 } };
deepAssign(options)(x,y); // { a: { a: 1, b: 1 } }
RaphaMex
quelle
13

Ich benutze lodash:

import _ = require('lodash');
value = _.merge(value1, value2);
Jeff Tian
quelle
2
Beachten Sie, dass das Zusammenführen das Objekt ändert, wenn Sie etwas möchten, das das Objekt nicht mutiert, dann _cloneDeep(value1).merge(value2)
Geckos
3
@geckos Sie können _.merge ({}, Wert1, Wert2)
Spenhouet
10

Hier ist die TypeScript-Implementierung:

export const mergeObjects = <T extends object = object>(target: T, ...sources: T[]): T  => {
  if (!sources.length) {
    return target;
  }
  const source = sources.shift();
  if (source === undefined) {
    return target;
  }

  if (isMergebleObject(target) && isMergebleObject(source)) {
    Object.keys(source).forEach(function(key: string) {
      if (isMergebleObject(source[key])) {
        if (!target[key]) {
          target[key] = {};
        }
        mergeObjects(target[key], source[key]);
      } else {
        target[key] = source[key];
      }
    });
  }

  return mergeObjects(target, ...sources);
};

const isObject = (item: any): boolean => {
  return item !== null && typeof item === 'object';
};

const isMergebleObject = (item): boolean => {
  return isObject(item) && !Array.isArray(item);
};

Und Unit Tests:

describe('merge', () => {
  it('should merge Objects and all nested Ones', () => {
    const obj1 = { a: { a1: 'A1'}, c: 'C', d: {} };
    const obj2 = { a: { a2: 'A2'}, b: { b1: 'B1'}, d: null };
    const obj3 = { a: { a1: 'A1', a2: 'A2'}, b: { b1: 'B1'}, c: 'C', d: null};
    expect(mergeObjects({}, obj1, obj2)).toEqual(obj3);
  });
  it('should behave like Object.assign on the top level', () => {
    const obj1 = { a: { a1: 'A1'}, c: 'C'};
    const obj2 = { a: undefined, b: { b1: 'B1'}};
    expect(mergeObjects({}, obj1, obj2)).toEqual(Object.assign({}, obj1, obj2));
  });
  it('should not merge array values, just override', () => {
    const obj1 = {a: ['A', 'B']};
    const obj2 = {a: ['C'], b: ['D']};
    expect(mergeObjects({}, obj1, obj2)).toEqual({a: ['C'], b: ['D']});
  });
  it('typed merge', () => {
    expect(mergeObjects<TestPosition>(new TestPosition(0, 0), new TestPosition(1, 1)))
      .toEqual(new TestPosition(1, 1));
  });
});

class TestPosition {
  constructor(public x: number = 0, public y: number = 0) {/*empty*/}
}
am0wa
quelle
8

Ich möchte eine ziemlich einfache ES5-Alternative vorstellen. Die Funktion erhält 2 Parameter - targetund sourcedas muss vom Typ "Objekt" sein. Targetwird das resultierende Objekt sein. Targetbehält alle ursprünglichen Eigenschaften bei, aber ihre Werte können geändert werden.

function deepMerge(target, source) {
if(typeof target !== 'object' || typeof source !== 'object') return false; // target or source or both ain't objects, merging doesn't make sense
for(var prop in source) {
  if(!source.hasOwnProperty(prop)) continue; // take into consideration only object's own properties.
  if(prop in target) { // handling merging of two properties with equal names
    if(typeof target[prop] !== 'object') {
      target[prop] = source[prop];
    } else {
      if(typeof source[prop] !== 'object') {
        target[prop] = source[prop];
      } else {
        if(target[prop].concat && source[prop].concat) { // two arrays get concatenated
          target[prop] = target[prop].concat(source[prop]);
        } else { // two objects get merged recursively
          target[prop] = deepMerge(target[prop], source[prop]); 
        } 
      }  
    }
  } else { // new properties get added to target
    target[prop] = source[prop]; 
  }
}
return target;
}

Fälle:

  • Wenn Sie targetkeine sourceEigenschaft haben, erhalten targetSie diese.
  • Wenn targeteine sourceEigenschaft vorhanden ist und target& sourcenicht beide Objekte sind (3 von 4 Fällen), wird targetdie Eigenschaft überschrieben.
  • Wenn targeteine sourceEigenschaft vorhanden ist und beide Objekte / Arrays sind (1 verbleibender Fall), erfolgt eine Rekursion, bei der zwei Objekte zusammengeführt werden (oder zwei Arrays verkettet werden).

Beachten Sie auch Folgendes :

  1. Array + obj = Array
  2. obj + array = obj
  3. obj + obj = obj (rekursiv zusammengeführt)
  4. Array + Array = Array (Concat)

Es ist vorhersehbar, unterstützt primitive Typen sowie Arrays und Objekte. Da wir auch 2 Objekte zusammenführen können, denke ich, dass wir über die Reduktionsfunktion mehr als 2 zusammenführen können.

Schauen Sie sich ein Beispiel an (und spielen Sie damit herum, wenn Sie möchten) :

var a = {
   "a_prop": 1,
   "arr_prop": [4, 5, 6],
   "obj": {
     "a_prop": {
       "t_prop": 'test'
     },
     "b_prop": 2
   }
};

var b = {
   "a_prop": 5,
   "arr_prop": [7, 8, 9],
   "b_prop": 15,
   "obj": {
     "a_prop": {
       "u_prop": false
     },
     "b_prop": {
        "s_prop": null
     }
   }
};

function deepMerge(target, source) {
    if(typeof target !== 'object' || typeof source !== 'object') return false;
    for(var prop in source) {
    if(!source.hasOwnProperty(prop)) continue;
      if(prop in target) {
        if(typeof target[prop] !== 'object') {
          target[prop] = source[prop];
        } else {
          if(typeof source[prop] !== 'object') {
            target[prop] = source[prop];
          } else {
            if(target[prop].concat && source[prop].concat) {
              target[prop] = target[prop].concat(source[prop]);
            } else {
              target[prop] = deepMerge(target[prop], source[prop]); 
            } 
          }  
        }
      } else {
        target[prop] = source[prop]; 
      }
    }
  return target;
}

console.log(deepMerge(a, b));

Es gibt eine Einschränkung - die Länge des Aufrufstapels des Browsers. Moderne Browser geben einen Fehler auf einer wirklich tiefen Rekursionsstufe aus (denken Sie an Tausende verschachtelter Aufrufe). Sie können auch Situationen wie Array + Objekt usw. nach Belieben behandeln, indem Sie neue Bedingungen und Typprüfungen hinzufügen.

Curveball
quelle
8

Hier ist eine weitere ES6-Lösung, die mit Objekten und Arrays arbeitet.

function deepMerge(...sources) {
  let acc = {}
  for (const source of sources) {
    if (source instanceof Array) {
      if (!(acc instanceof Array)) {
        acc = []
      }
      acc = [...acc, ...source]
    } else if (source instanceof Object) {
      for (let [key, value] of Object.entries(source)) {
        if (value instanceof Object && key in acc) {
          value = deepMerge(acc[key], value)
        }
        acc = { ...acc, [key]: value }
      }
    }
  }
  return acc
}
Pravdomil
quelle
3
ist dies getestet und / oder Teil einer Bibliothek, sieht gut aus, würde aber gerne sicherstellen, dass es etwas bewiesen ist.
7

Wenn Sie ImmutableJS verwenden , können Sie Folgendes verwenden mergeDeep:

fromJS(options).mergeDeep(options2).toJS();
Dimitri Kopriwa
quelle
2
@ EliseChant Das glaube ich nicht. Warum klärst du nicht?
Dimitri Kopriwa
7

Gibt es eine Möglichkeit, dies zu tun?

Wenn npm-Bibliotheken als Lösung verwendet werden können, können Sie mit Object-Merge-Advanced von Ihnen Objekte wirklich gründlich zusammenführen und jede einzelne Zusammenführungsaktion mithilfe einer bekannten Rückruffunktion anpassen / überschreiben. Die Hauptidee ist es mehr als nur tiefe Verschmelzung - was mit dem Wert passiert , wenn zwei Tasten sind die gleichen ? Diese Bibliothek kümmert sich darum - wenn zwei Schlüssel zusammenstoßen, werden object-merge-advanceddie Typen gewogen, um nach dem Zusammenführen so viele Daten wie möglich zu erhalten:

Zusammenführen von Objektschlüssel-Wägeschlüsselwerttypen, um so viele Daten wie möglich zu erhalten

Der Schlüssel des ersten Eingabearguments ist mit # 1 markiert, der des zweiten Arguments mit # 2. Abhängig von jedem Typ wird einer für den Wert des Ergebnisschlüssels ausgewählt. Im Diagramm bedeutet "ein Objekt" ein einfaches Objekt (kein Array usw.).

Wenn die Tasten nicht zusammenstoßen, geben sie alle das Ergebnis ein.

Wenn Sie aus Ihrem Beispiel-Snippet object-merge-advancedIhr Code-Snippet zusammengeführt haben:

const mergeObj = require("object-merge-advanced");
const x = { a: { a: 1 } };
const y = { a: { b: 1 } };
const res = console.log(mergeObj(x, y));
// => res = {
//      a: {
//        a: 1,
//        b: 1
//      }
//    }

Der Algorithmus durchläuft rekursiv alle Eingabeobjektschlüssel, vergleicht und erstellt das neue zusammengeführte Ergebnis und gibt es zurück.

revelt
quelle
6

Die folgende Funktion erstellt eine tiefe Kopie von Objekten. Sie umfasst das Kopieren von Grundelementen, Arrays und Objekten

 function mergeDeep (target, source)  {
    if (typeof target == "object" && typeof source == "object") {
        for (const key in source) {
            if (source[key] === null && (target[key] === undefined || target[key] === null)) {
                target[key] = null;
            } else if (source[key] instanceof Array) {
                if (!target[key]) target[key] = [];
                //concatenate arrays
                target[key] = target[key].concat(source[key]);
            } else if (typeof source[key] == "object") {
                if (!target[key]) target[key] = {};
                this.mergeDeep(target[key], source[key]);
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}
sudharsan tk
quelle
6

Eine einfache Lösung mit ES5 (vorhandenen Wert überschreiben):

function merge(current, update) {
  Object.keys(update).forEach(function(key) {
    // if update[key] exist, and it's not a string or array,
    // we go in one level deeper
    if (current.hasOwnProperty(key) 
        && typeof current[key] === 'object'
        && !(current[key] instanceof Array)) {
      merge(current[key], update[key]);

    // if update[key] doesn't exist in current, or it's a string
    // or array, then assign/overwrite current[key] to update[key]
    } else {
      current[key] = update[key];
    }
  });
  return current;
}

var x = { a: { a: 1 } }
var y = { a: { b: 1 } }

console.log(merge(x, y));

yc
quelle
genau das, was ich brauchte - es6 verursachte Probleme beim Build - diese es5-Alternative ist die Bombe
danday74
5

Die meisten Beispiele hier scheinen zu komplex zu sein. Ich verwende eines in TypeScript, das ich erstellt habe. Ich denke, es sollte die meisten Fälle abdecken (ich behandle Arrays als reguläre Daten und ersetze sie nur).

const isObject = (item: any) => typeof item === 'object' && !Array.isArray(item);

export const merge = <A = Object, B = Object>(target: A, source: B): A & B => {
  const isDeep = (prop: string) =>
    isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
  const replaced = Object.getOwnPropertyNames(source)
    .map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
    .reduce((a, b) => ({ ...a, ...b }), {});

  return {
    ...(target as Object),
    ...(replaced as Object)
  } as A & B;
};

Das Gleiche in einfachem JS, nur für den Fall:

const isObject = item => typeof item === 'object' && !Array.isArray(item);

const merge = (target, source) => {
  const isDeep = prop => 
    isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
  const replaced = Object.getOwnPropertyNames(source)
    .map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
    .reduce((a, b) => ({ ...a, ...b }), {});

  return {
    ...target,
    ...replaced
  };
};

Hier sind meine Testfälle, um zu zeigen, wie Sie es verwenden können

describe('merge', () => {
  context('shallow merges', () => {
    it('merges objects', () => {
      const a = { a: 'discard' };
      const b = { a: 'test' };
      expect(merge(a, b)).to.deep.equal({ a: 'test' });
    });
    it('extends objects', () => {
      const a = { a: 'test' };
      const b = { b: 'test' };
      expect(merge(a, b)).to.deep.equal({ a: 'test', b: 'test' });
    });
    it('extends a property with an object', () => {
      const a = { a: 'test' };
      const b = { b: { c: 'test' } };
      expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
    });
    it('replaces a property with an object', () => {
      const a = { b: 'whatever', a: 'test' };
      const b = { b: { c: 'test' } };
      expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
    });
  });

  context('deep merges', () => {
    it('merges objects', () => {
      const a = { test: { a: 'discard', b: 'test' }  };
      const b = { test: { a: 'test' } } ;
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
    });
    it('extends objects', () => {
      const a = { test: { a: 'test' } };
      const b = { test: { b: 'test' } };
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
    });
    it('extends a property with an object', () => {
      const a = { test: { a: 'test' } };
      const b = { test: { b: { c: 'test' } } };
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
    });
    it('replaces a property with an object', () => {
      const a = { test: { b: 'whatever', a: 'test' } };
      const b = { test: { b: { c: 'test' } } };
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
    });
  });
});

Bitte lassen Sie mich wissen, wenn Sie glauben, dass mir einige Funktionen fehlen.

Ezequiel
quelle
5

Wenn Sie einen Einzeiler haben möchten, ohne eine große Bibliothek wie lodash zu benötigen, empfehle ich Ihnen, deepmerge zu verwenden . ( npm install deepmerge)

Dann können Sie tun

deepmerge({ a: 1, b: 2, c: 3 }, { a: 2, d: 3 });

bekommen

{ a: 2, b: 2, c: 3, d: 3 }

Das Schöne ist, dass es sofort mit Typing für TypeScript kommt. Es ermöglicht auch das Zusammenführen von Arrays . Dies ist eine echte Allrounder-Lösung.

Martin Braun
quelle
4

Wir können $ .extend (true, object1, object2) für die Tiefenverschmelzung verwenden. Der Wert true bedeutet, dass zwei Objekte rekursiv zusammengeführt werden, wobei das erste geändert wird.

$ verlängern (wahr, Ziel, Objekt)

Abinaya
quelle
9
Der Fragesteller hat nie angegeben, dass er jquery verwendet, und scheint nach einer nativen Javascript-Lösung zu fragen.
JoE
Dies ist eine sehr einfache Methode und funktioniert. Eine praktikable Lösung, die ich in Betracht ziehen würde, wenn ich diese Frage stellen würde. :)
Kashiraja
Dies ist eine sehr gute Antwort, es fehlt jedoch ein Link zum Quellcode zu jQuery. In jQuery arbeiten viele Leute an dem Projekt, und sie haben einige Zeit damit verbracht, tiefes Kopieren richtig zu machen. Außerdem ist der Quellcode ziemlich "einfach": github.com/jquery/jquery/blob/master/src/core.js#L125 "Einfach" steht in Anführungszeichen, da es beim Stöbern kompliziert wird jQuery.isPlainObject(). Dies zeigt die Komplexität der Bestimmung, ob etwas ein einfaches Objekt ist oder nicht, was die meisten Antworten hier bei weitem übersehen. Ratet mal, in welcher Sprache jQuery geschrieben ist?
CubicleSoft
4

Hier eine einfache Lösung, die wie ein Object.assignDeeep funktioniert und für ein Array ohne Änderungen funktioniert

function deepAssign(target, ...sources) {
    for( source of sources){
        for(let k in source){
            let vs = source[k], vt = target[k];
            if(Object(vs)== vs && Object(vt)===vt ){
                target[k] = deepAssign(vt, vs)
                continue;
            }
            target[k] = source[k];
        }    
    }
    return target;
}

Beispiel

x = { a: { a: 1 }, b:[1,2] };
y = { a: { b: 1 }, b:[3] };
z = {c:3,b:[,,,4]}
x = deepAssign(x,y,z)
// x will be
x ==  {
  "a": {
    "a": 1,
    "b": 1
  },
  "b": [    1,    2,    null,    4  ],
  "c": 3
}

pery mimon
quelle
3

Ich hatte dieses Problem beim Laden eines zwischengespeicherten Redux-Status. Wenn ich nur den zwischengespeicherten Status lade, treten bei der neuen App-Version mit einer aktualisierten Statusstruktur Fehler auf.

Es wurde bereits erwähnt, dass lodash die mergeFunktion bietet , die ich verwendet habe:

const currentInitialState = configureState().getState();
const mergedState = _.merge({}, currentInitialState, cachedState);
const store = configureState(mergedState);
embiem
quelle
3

Viele Antworten verwenden mehrere zehn Codezeilen oder erfordern das Hinzufügen einer neuen Bibliothek zum Projekt. Wenn Sie jedoch die Rekursion verwenden, sind dies nur vier Codezeilen.

function merge(current, updates) {
  for (key of Object.keys(updates)) {
    if (!current.hasOwnProperty(key) || typeof updates[key] !== 'object') current[key] = updates[key];
    else merge(current[key], updates[key]);
  }
  return current;
}
console.log(merge({ a: { a: 1 } }, { a: { b: 1 } }));

Arrays-Behandlung: Die obige Version überschreibt alte Array-Werte mit neuen. Wenn Sie möchten, dass die alten Array-Werte beibehalten und die neuen hinzugefügt werden, fügen Sie einfach einen else if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key])Block über dem elseStatuen hinzu, und schon sind Sie fertig.

Vincent
quelle
1
Ich mag es, aber es braucht eine einfache undefinierte Prüfung für 'aktuell' oder {foo: undefined} wird nicht zusammengeführt. Fügen Sie einfach ein if (aktuell) vor der for-Schleife hinzu.
Andreas Pardeike
Danke für den Vorschlag
Vincent
2

Hier ist eine andere, die ich gerade geschrieben habe und die Arrays unterstützt. Es konzentriert sie.

function isObject(obj) {
    return obj !== null && typeof obj === 'object';
}


function isPlainObject(obj) {
    return isObject(obj) && (
        obj.constructor === Object  // obj = {}
        || obj.constructor === undefined // obj = Object.create(null)
    );
}

function mergeDeep(target, ...sources) {
    if (!sources.length) return target;
    const source = sources.shift();

    if(Array.isArray(target)) {
        if(Array.isArray(source)) {
            target.push(...source);
        } else {
            target.push(source);
        }
    } else if(isPlainObject(target)) {
        if(isPlainObject(source)) {
            for(let key of Object.keys(source)) {
                if(!target[key]) {
                    target[key] = source[key];
                } else {
                    mergeDeep(target[key], source[key]);
                }
            }
        } else {
            throw new Error(`Cannot merge object with non-object`);
        }
    } else {
        target = source;
    }

    return mergeDeep(target, ...sources);
};
mpen
quelle
2

Verwenden Sie diese Funktion:

merge(target, source, mutable = false) {
        const newObj = typeof target == 'object' ? (mutable ? target : Object.assign({}, target)) : {};
        for (const prop in source) {
            if (target[prop] == null || typeof target[prop] === 'undefined') {
                newObj[prop] = source[prop];
            } else if (Array.isArray(target[prop])) {
                newObj[prop] = source[prop] || target[prop];
            } else if (target[prop] instanceof RegExp) {
                newObj[prop] = source[prop] || target[prop];
            } else {
                newObj[prop] = typeof source[prop] === 'object' ? this.merge(target[prop], source[prop]) : source[prop];
            }
        }
        return newObj;
    }
Vikram Biwal
quelle
2

Ramda, eine schöne Bibliothek von Javascript-Funktionen, hat mergeDeepLeft und mergeDeepRight. Alle diese funktionieren ziemlich gut für dieses Problem. Bitte sehen Sie sich die Dokumentation hier an: https://ramdajs.com/docs/#mergeDeepLeft

Für das spezifische Beispiel können wir verwenden:

import { mergeDeepLeft } from 'ramda'
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = mergeDeepLeft(x, y)) // {"a":{"a":1,"b":1}}
afonte
quelle
2
// copies all properties from source object to dest object recursively
export function recursivelyMoveProperties(source, dest) {
  for (const prop in source) {
    if (!source.hasOwnProperty(prop)) {
      continue;
    }

    if (source[prop] === null) {
      // property is null
      dest[prop] = source[prop];
      continue;
    }

    if (typeof source[prop] === 'object') {
      // if property is object let's dive into in
      if (Array.isArray(source[prop])) {
        dest[prop] = [];
      } else {
        if (!dest.hasOwnProperty(prop)
        || typeof dest[prop] !== 'object'
        || dest[prop] === null || Array.isArray(dest[prop])
        || !Object.keys(dest[prop]).length) {
          dest[prop] = {};
        }
      }
      recursivelyMoveProperties(source[prop], dest[prop]);
      continue;
    }

    // property is simple type: string, number, e.t.c
    dest[prop] = source[prop];
  }
  return dest;
}

Gerätetest:

describe('recursivelyMoveProperties', () => {
    it('should copy properties correctly', () => {
      const source: any = {
        propS1: 'str1',
        propS2: 'str2',
        propN1: 1,
        propN2: 2,
        propA1: [1, 2, 3],
        propA2: [],
        propB1: true,
        propB2: false,
        propU1: null,
        propU2: null,
        propD1: undefined,
        propD2: undefined,
        propO1: {
          subS1: 'sub11',
          subS2: 'sub12',
          subN1: 11,
          subN2: 12,
          subA1: [11, 12, 13],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
        propO2: {
          subS1: 'sub21',
          subS2: 'sub22',
          subN1: 21,
          subN2: 22,
          subA1: [21, 22, 23],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
      };
      let dest: any = {
        propS2: 'str2',
        propS3: 'str3',
        propN2: -2,
        propN3: 3,
        propA2: [2, 2],
        propA3: [3, 2, 1],
        propB2: true,
        propB3: false,
        propU2: 'not null',
        propU3: null,
        propD2: 'defined',
        propD3: undefined,
        propO2: {
          subS2: 'inv22',
          subS3: 'sub23',
          subN2: -22,
          subN3: 23,
          subA2: [5, 5, 5],
          subA3: [31, 32, 33],
          subB2: false,
          subB3: true,
          subU2: 'not null --- ',
          subU3: null,
          subD2: ' not undefined ----',
          subD3: undefined,
        },
        propO3: {
          subS1: 'sub31',
          subS2: 'sub32',
          subN1: 31,
          subN2: 32,
          subA1: [31, 32, 33],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
      };
      dest = recursivelyMoveProperties(source, dest);

      expect(dest).toEqual({
        propS1: 'str1',
        propS2: 'str2',
        propS3: 'str3',
        propN1: 1,
        propN2: 2,
        propN3: 3,
        propA1: [1, 2, 3],
        propA2: [],
        propA3: [3, 2, 1],
        propB1: true,
        propB2: false,
        propB3: false,
        propU1: null,
        propU2: null,
        propU3: null,
        propD1: undefined,
        propD2: undefined,
        propD3: undefined,
        propO1: {
          subS1: 'sub11',
          subS2: 'sub12',
          subN1: 11,
          subN2: 12,
          subA1: [11, 12, 13],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
        propO2: {
          subS1: 'sub21',
          subS2: 'sub22',
          subS3: 'sub23',
          subN1: 21,
          subN2: 22,
          subN3: 23,
          subA1: [21, 22, 23],
          subA2: [],
          subA3: [31, 32, 33],
          subB1: false,
          subB2: true,
          subB3: true,
          subU1: null,
          subU2: null,
          subU3: null,
          subD1: undefined,
          subD2: undefined,
          subD3: undefined,
        },
        propO3: {
          subS1: 'sub31',
          subS2: 'sub32',
          subN1: 31,
          subN2: 32,
          subA1: [31, 32, 33],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
      });
    });
  });
Sergey Gurin
quelle
2

Ich habe nur eine zweizeilige Lösung gefunden, um eine tiefe Zusammenführung in Javascript zu erreichen. Lassen Sie mich wissen, wie das für Sie funktioniert.

const obj1 = { a: { b: "c", x: "y" } }
const obj2 = { a: { b: "d", e: "f" } }
temp = Object.assign({}, obj1, obj2)
Object.keys(temp).forEach(key => {
    temp[key] = (typeof temp[key] === 'object') ? Object.assign(temp[key], obj1[key], obj2[key]) : temp[key])
}
console.log(temp)

Das temporäre Objekt druckt {a: {b: 'd', e: 'f', x: 'y'}}

saumilsdk
quelle
1
Dies führt keine eigentliche Tiefenverschmelzung durch. Es wird mit scheitern merge({x:{y:{z:1}}}, {x:{y:{w:2}}}). Il kann auch vorhandene Werte in obj1 nicht aktualisieren, wenn obj2 sie auch hat, zum Beispiel mit merge({x:{y:1}}, {x:{y:2}}).
Oreilles
1

Manchmal brauchen Sie keine tiefe Verschmelzung, selbst wenn Sie so denken. Wenn Sie beispielsweise eine Standardkonfiguration mit verschachtelten Objekten haben und diese mit Ihrer eigenen Konfiguration erweitern möchten, können Sie eine Klasse dafür erstellen. Das Konzept ist sehr einfach:

function AjaxConfig(config) {

  // Default values + config

  Object.assign(this, {
    method: 'POST',
    contentType: 'text/plain'
  }, config);

  // Default values in nested objects

  this.headers = Object.assign({}, this.headers, { 
    'X-Requested-With': 'custom'
  });
}

// Define your config

var config = {
  url: 'https://google.com',
  headers: {
    'x-client-data': 'CI22yQEI'
  }
};

// Extend the default values with your own
var fullMergedConfig = new AjaxConfig(config);

// View in DevTools
console.log(fullMergedConfig);

Sie können es in eine Funktion konvertieren (keinen Konstruktor).

Ruslan
quelle
1

Dies ist eine billige Deep Merge, die so wenig Code verwendet, wie ich mir vorstellen kann. Jede Quelle überschreibt die vorherige Eigenschaft, wenn sie vorhanden ist.

const { keys } = Object;

const isObject = a => typeof a === "object" && !Array.isArray(a);
const merge = (a, b) =>
  isObject(a) && isObject(b)
    ? deepMerge(a, b)
    : isObject(a) && !isObject(b)
    ? a
    : b;

const coalesceByKey = source => (acc, key) =>
  (acc[key] && source[key]
    ? (acc[key] = merge(acc[key], source[key]))
    : (acc[key] = source[key])) && acc;

/**
 * Merge all sources into the target
 * overwriting primitive values in the the accumulated target as we go (if they already exist)
 * @param {*} target
 * @param  {...any} sources
 */
const deepMerge = (target, ...sources) =>
  sources.reduce(
    (acc, source) => keys(source).reduce(coalesceByKey(source), acc),
    target
  );

console.log(deepMerge({ a: 1 }, { a: 2 }));
console.log(deepMerge({ a: 1 }, { a: { b: 2 } }));
console.log(deepMerge({ a: { b: 2 } }, { a: 1 }));
Lewis
quelle
1

Ich verwende die folgende kurze Funktion zum tiefen Zusammenführen von Objekten.
Es funktioniert großartig für mich.
Der Autor erklärt hier vollständig, wie es funktioniert.

/*!
 * Merge two or more objects together.
 * (c) 2017 Chris Ferdinandi, MIT License, https://gomakethings.com
 * @param   {Boolean}  deep     If true, do a deep (or recursive) merge [optional]
 * @param   {Object}   objects  The objects to merge together
 * @returns {Object}            Merged values of defaults and options
 * 
 * Use the function as follows:
 * let shallowMerge = extend(obj1, obj2);
 * let deepMerge = extend(true, obj1, obj2)
 */

var extend = function () {

    // Variables
    var extended = {};
    var deep = false;
    var i = 0;

    // Check if a deep merge
    if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) {
        deep = arguments[0];
        i++;
    }

    // Merge the object into the extended object
    var merge = function (obj) {
        for (var prop in obj) {
            if (obj.hasOwnProperty(prop)) {
                // If property is an object, merge properties
                if (deep && Object.prototype.toString.call(obj[prop]) === '[object Object]') {
                    extended[prop] = extend(extended[prop], obj[prop]);
                } else {
                    extended[prop] = obj[prop];
                }
            }
        }
    };

    // Loop through each object and conduct a merge
    for (; i < arguments.length; i++) {
        merge(arguments[i]);
    }

    return extended;

};
John Shearing
quelle
Während dieser Link die Frage beantworten kann, ist es besser, die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen. Nur-Link-Antworten können ungültig werden, wenn sich die verknüpfte Seite ändert. - Aus dem Rückblick
Chris Camaratta
Hallo @ChrisCamaratta. Hier geht es nicht nur um das Wesentliche, sondern auch um die Funktion und deren Verwendung. Dies ist also definitiv keine Link-Antwort. Dies ist die Funktion, mit der ich Objekte tief zusammengeführt habe. Der Link ist nur, wenn Sie möchten, dass der Autor erklärt, wie es funktioniert. Ich denke, es wäre ein schlechter Dienst für die Community, die Funktionsweise besser zu erklären als der Autor, der JavaScript unterrichtet. Danke für den Kommentar.
John Shearing
Huh. Entweder habe ich es verpasst oder der Code wurde beim Überprüfen nicht in der Überprüfungsoberfläche angezeigt. Ich bin damit einverstanden, dass dies eine qualitativ hochwertige Antwort ist. Es scheint, dass andere Rezensenten meine anfängliche Einschätzung außer Kraft gesetzt haben, also denke ich, dass Sie in Ordnung sind. Entschuldigung für die Inspirationsflagge.
Chris Camaratta
Großartig! @ ChrisCamaratta, Danke, dass du mir geholfen hast zu verstehen, was passiert ist.
John Shearing