diff --git a/merge.go b/merge.go index 4b47d0b..c4481c8 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 @@ -189,7 +191,39 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co return } } + } 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).IsValid() && srcElem.IsValid() { + dstSlice.Index(i).Set(srcElem) + } else { + if err = deepMerge(dstElem, srcElem, visited, depth+1, config); err != nil { + return + } else if dstSlice.Index(i).IsValid() && dstElem.IsValid() { + dstSlice.Index(i).Set(dstElem) + } + } + } } dst.SetMapIndex(key, dstSlice) } @@ -231,6 +265,38 @@ 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 + } + dst.Index(i).Set(dstElem) + } + } } case reflect.Ptr: fallthrough @@ -342,6 +408,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 diff --git a/pr180_test.go b/pr180_test.go new file mode 100644 index 0000000..81ad547 --- /dev/null +++ b/pr180_test.go @@ -0,0 +1,56 @@ +package mergo_test + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/imdario/mergo" +) + +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{}{ + {"c": "2"}, + }, + } + + src := map[string]interface{}{ + "b": []map[string]interface{}{ + {"c": "3", "d": "1"}, + {"e": "1", "f": "1", "g": []string{"1", "2"}}, + }, + } + + if err := mergo.Merge(&dst, src, mergo.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'") + } +}