From 292493a540389092404691397763f1b85c33c76b Mon Sep 17 00:00:00 2001 From: Arnaud Briche Date: Thu, 4 Mar 2021 11:17:25 +0100 Subject: [PATCH 1/2] Add new WithSliceDeepMerge config option --- merge.go | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/merge.go b/merge.go index 8c2a8fc..c84d30a 100644 --- a/merge.go +++ b/merge.go @@ -45,6 +45,7 @@ type Config struct { overwriteWithEmptyValue bool overwriteSliceWithEmptyValue bool sliceDeepCopy bool + sliceDeepMerge bool debug bool } @@ -61,6 +62,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co overwriteWithEmptySrc := config.overwriteWithEmptyValue overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue sliceDeepCopy := config.sliceDeepCopy + sliceDeepMerge := config.sliceDeepMerge if !src.IsValid() { return @@ -190,6 +192,39 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co } } + } else if sliceDeepMerge { + if srcSlice.Len() > dstSlice.Len() { + newSlice := reflect.MakeSlice(srcSlice.Type(), srcSlice.Len(), srcSlice.Len()) + + for i := 0; i < dstSlice.Len(); i++ { + newSlice.Index(i).Set(dstSlice.Index(i)) + } + + dstSlice = newSlice + } + + for i := 0; i < srcSlice.Len(); i++ { + srcElem := srcSlice.Index(i) + dstElem := dstSlice.Index(i) + + if srcElem.CanInterface() { + srcElem = reflect.ValueOf(srcElem.Interface()) + } + + if dstElem.CanInterface() { + dstElem = reflect.ValueOf(dstElem.Interface()) + } + + if dstSlice.Index(i).IsZero() { + dstSlice.Index(i).Set(srcElem) + } else { + if err = deepMerge(dstElem, srcElem, visited, depth+1, config); err != nil { + return + } else { + dstSlice.Index(i).Set(dstElem) + } + } + } } dst.SetMapIndex(key, dstSlice) } @@ -231,6 +266,39 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co return } } + } else if sliceDeepMerge { + 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)) + } + + dst = newSlice + } + + for i := 0; i < src.Len(); i++ { + srcElem := src.Index(i) + dstElem := dst.Index(i) + + if srcElem.CanInterface() { + srcElem = reflect.ValueOf(srcElem.Interface()) + } + + if dstElem.CanInterface() { + dstElem = reflect.ValueOf(dstElem.Interface()) + } + + if dst.Index(i).IsZero() { + dst.Index(i).Set(srcElem) + } else { + if err = deepMerge(dstElem, srcElem, visited, depth+1, config); err != nil { + return + } else { + dst.Index(i).Set(dstElem) + } + } + } } case reflect.Ptr: fallthrough @@ -342,6 +410,11 @@ func WithSliceDeepCopy(config *Config) { config.Overwrite = true } +// WithSliceDeepMerge will make merge deep merge slice elements pairwise (resizing dst slice if needed) +func WithSliceDeepMerge(config *Config) { + config.sliceDeepMerge = true +} + func merge(dst, src interface{}, opts ...func(*Config)) error { if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { return ErrNonPointerAgument From 46a89e6901133e60bc2c7254492c83a84b82b4ca Mon Sep 17 00:00:00 2001 From: Arnaud Briche Date: Thu, 4 Mar 2021 11:19:39 +0100 Subject: [PATCH 2/2] Add tests for WithSliceDeepMerge option --- pr180_test.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 pr180_test.go diff --git a/pr180_test.go b/pr180_test.go new file mode 100644 index 0000000..2ba9fd5 --- /dev/null +++ b/pr180_test.go @@ -0,0 +1,54 @@ +package mergo + +import ( + "encoding/json" + "fmt" + "testing" +) + +func pp(i interface{}) string { + b, _ := json.MarshalIndent(i, "", " ") + return string(b) +} + +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, WithSliceDeepMerge); err != nil { + t.Fatalf("Error during the merge: %v", err) + } + + fmt.Println(pp(dst)) + + 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'") + } +}