Skip to content

Commit

Permalink
Added sliceDeepMerge feature, as seen in darccio#180
Browse files Browse the repository at this point in the history
Added validations to some reflection calls that were causing panic: "reflect: call of reflect.Value.IsZero on zero Value"
  • Loading branch information
drenizg committed Aug 25, 2022
1 parent 1e82098 commit 96195c4
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 0 deletions.
71 changes: 71 additions & 0 deletions merge.go
Expand Up @@ -45,6 +45,7 @@ type Config struct {
overwriteWithEmptyValue bool
overwriteSliceWithEmptyValue bool
sliceDeepCopy bool
sliceDeepMerge bool
debug bool
}

Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
56 changes: 56 additions & 0 deletions 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'")
}
}

0 comments on commit 96195c4

Please sign in to comment.