Repository: imdario/mergo
Pull request #151 (2020-06-22 09:12:16)

Issue 121


Patch:
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..11768bf
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module github.com/aivetech/mergo
+
+go 1.13
+
+require gopkg.in/yaml.v2 v2.3.0
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..8fabe8d
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,3 @@
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/merge.go b/merge.go
index 1f689db..4ab8c0b 100644
--- a/merge.go
+++ b/merge.go
@@ -46,6 +46,7 @@ type Config struct {
 	TypeCheck                    bool
 	overwriteWithEmptyValue      bool
 	overwriteSliceWithEmptyValue bool
+	deepMergeSlice               bool
 	Transformers                 Transformers
 }
 
@@ -207,6 +208,45 @@ func deepMerge(dstIn, src reflect.Value, visited map[uintptr]*visit, depth int,
 				return
 			}
 			newSlice = reflect.AppendSlice(dst, src)
+		} else if config.deepMergeSlice {
+			if typeCheck && src.Type() != dst.Type() {
+				err = fmt.Errorf("cannot deep merge two slice with different type (%s, %s)", src.Type(), dst.Type())
+				return
+			}
+
+			if src.Len() > dst.Len() {
+				newSlice = reflect.MakeSlice(dst.Type(), src.Len(), src.Len())
+
+				for i := 0; i < dst.Len(); i++ {
+					newSlice.Index(i).Set(dst.Index(i))
+				}
+			}
+
+			for i := 0; i < src.Len(); i++ {
+				srcElem := src.Index(i)
+				dstElem := newSlice.Index(i)
+
+				if srcElem.CanInterface() {
+					srcElem = reflect.ValueOf(srcElem.Interface())
+				}
+
+				if dstElem.CanInterface() {
+					dstElem = reflect.ValueOf(dstElem.Interface())
+				}
+
+				if newSlice.Index(i).IsZero() {
+					newSlice.Index(i).Set(srcElem)
+					continue
+				}
+
+				dstElem, err = deepMerge(dstElem, srcElem, visited, depth+1, config)
+
+				if err != nil {
+					return
+				}
+
+				newSlice.Index(i).Set(dstElem)
+			}
 		}
 
 		if dst.CanSet() {
@@ -328,6 +368,11 @@ func WithTypeCheck(config *Config) {
 	config.TypeCheck = true
 }
 
+// WithDeepMergeSlice will make merge deep merge slice elements pairwise (resizing dst slice if needed)
+func WithDeepMergeSlice(config *Config) {
+	config.deepMergeSlice = true
+}
+
 func merge(dst, src interface{}, opts ...func(*Config)) error {
 	if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
 		return ErrNonPointerArgument


Test patch:
diff --git a/issue121_test.go b/issue121_test.go
new file mode 100644
index 0000000..a30bd0e
--- /dev/null
+++ b/issue121_test.go
@@ -0,0 +1,45 @@
+package mergo
+
+import (
+	"testing"
+)
+
+func TestIssue121WithSliceDeepMerge(t *testing.T) {
+	dst := map[string]interface{}{
+		"a": "1",
+		"b": []map[string]interface{}{
+			map[string]interface{}{"c": "2"},
+		},
+	}
+
+	src := map[string]interface{}{
+		"b": []map[string]interface{}{
+			map[string]interface{}{"c": "3", "d": "1"},
+			map[string]interface{}{"e": "1", "f": "1", "g": []string{"1", "2"}},
+		},
+	}
+
+	if err := Merge(&dst, src, WithDeepMergeSlice); err != nil {
+		t.Fatalf("Error during the merge: %v", err)
+	}
+
+	if dst["a"].(string) != "1" {
+		t.Error("a should equal '1'")
+	}
+
+	if dst["b"].([]map[string]interface{})[0]["c"] != "2" {
+		t.Error("b.[0].c should equal '2'")
+	}
+
+	if dst["b"].([]map[string]interface{})[0]["d"] != "1" {
+		t.Error("b.[0].d should equal '2'")
+	}
+
+	if dst["b"].([]map[string]interface{})[1]["e"] != "1" {
+		t.Error("b.[1].e should equal '1'")
+	}
+
+	if dst["b"].([]map[string]interface{})[1]["g"].([]string)[0] != "1" {
+		t.Error("b.[1].g[0] should equal '1'")
+	}
+}