Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions issue233_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package mergo_test

import (
"reflect"
"testing"

"dario.cat/mergo"
)

type simpleStruct233 struct {
Field1 string
Field2 string
Field3 string
}

type structWithSlice233 struct {
Items []simpleStruct233
}

// TestWithSliceDeepMerge verifies that WithSliceDeepMerge merges overlapping
// slice elements without overriding non-empty destination fields and appends
// trailing source elements.
func TestWithSliceDeepMerge(t *testing.T) {
src := structWithSlice233{
Items: []simpleStruct233{
{Field1: "src0_f1", Field2: "src0_f2", Field3: ""},
{Field1: "src1_f1", Field2: "src1_f2", Field3: ""},
},
}
dst := structWithSlice233{
Items: []simpleStruct233{
{Field1: "dst0_f1", Field2: "", Field3: "dst0_f3"},
},
}
expected := structWithSlice233{
Items: []simpleStruct233{
{Field1: "dst0_f1", Field2: "src0_f2", Field3: "dst0_f3"},
{Field1: "src1_f1", Field2: "src1_f2", Field3: ""},
},
}

if err := mergo.Merge(&dst, src, mergo.WithSliceDeepMerge); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(dst, expected) {
t.Fatalf("expected %+v, got %+v", expected, dst)
}
}

// TestWithSliceDeepMergeAndOverride verifies that WithSliceDeepMerge combined
// with WithOverride overrides overlapping non-empty destination fields while
// appending trailing source elements.
func TestWithSliceDeepMergeAndOverride(t *testing.T) {
src := structWithSlice233{
Items: []simpleStruct233{
{Field1: "src0_f1", Field2: "src0_f2", Field3: ""},
{Field1: "src1_f1", Field2: "src1_f2", Field3: "src1_f3"},
},
}
dst := structWithSlice233{
Items: []simpleStruct233{
{Field1: "dst0_f1", Field2: "", Field3: "dst0_f3"},
},
}
expected := structWithSlice233{
Items: []simpleStruct233{
{Field1: "src0_f1", Field2: "src0_f2", Field3: "dst0_f3"},
{Field1: "src1_f1", Field2: "src1_f2", Field3: "src1_f3"},
},
}

if err := mergo.Merge(&dst, src, mergo.WithSliceDeepMerge, mergo.WithOverride); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(dst, expected) {
t.Fatalf("expected %+v, got %+v", expected, dst)
}
}

// TestWithSliceDeepMergeInterfaceStructs verifies that WithSliceDeepMerge merges
// struct values stored inside map-held []interface{} slices.
func TestWithSliceDeepMergeInterfaceStructs(t *testing.T) {
src := map[string]interface{}{
"items": []interface{}{
simpleStruct233{Field1: "src0_f1", Field2: "src0_f2", Field3: ""},
simpleStruct233{Field1: "src1_f1", Field2: "src1_f2", Field3: ""},
},
}
dst := map[string]interface{}{
"items": []interface{}{
simpleStruct233{Field1: "dst0_f1", Field2: "", Field3: "dst0_f3"},
},
}
expected := map[string]interface{}{
"items": []interface{}{
simpleStruct233{Field1: "dst0_f1", Field2: "src0_f2", Field3: "dst0_f3"},
simpleStruct233{Field1: "src1_f1", Field2: "src1_f2", Field3: ""},
},
}

if err := mergo.Merge(&dst, src, mergo.WithSliceDeepMerge); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(dst, expected) {
t.Fatalf("expected %+v, got %+v", expected, dst)
}
}

// TestWithSliceDeepMergeDirectInterfaceStructs verifies that WithSliceDeepMerge
// fills empty fields in struct values stored in a direct []interface{} struct field
// and appends trailing source elements.
func TestWithSliceDeepMergeDirectInterfaceStructs(t *testing.T) {
type holder struct {
Items []interface{}
}
dst := holder{Items: []interface{}{
simpleStruct233{Field1: "dst0_f1", Field2: "", Field3: "dst0_f3"},
nil,
}}
src := holder{Items: []interface{}{
simpleStruct233{Field1: "src0_f1", Field2: "src0_f2", Field3: ""},
"filled_nil_slot",
simpleStruct233{Field1: "src1_f1", Field2: "src1_f2", Field3: ""},
}}
expected := holder{Items: []interface{}{
simpleStruct233{Field1: "dst0_f1", Field2: "src0_f2", Field3: "dst0_f3"},
"filled_nil_slot",
simpleStruct233{Field1: "src1_f1", Field2: "src1_f2", Field3: ""},
}}

if err := mergo.Merge(&dst, src, mergo.WithSliceDeepMerge); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(dst, expected) {
t.Fatalf("expected %+v, got %+v", expected, dst)
}
}

// TestWithSliceDeepMergeIncompatibleInterfaceStructError verifies that an incompatible
// source element in a []interface{} slice returns an error instead of panicking.
func TestWithSliceDeepMergeIncompatibleInterfaceStructError(t *testing.T) {
type holder struct {
Items []interface{}
}
dst := holder{Items: []interface{}{
simpleStruct233{Field1: "dst0_f1"},
}}
src := holder{Items: []interface{}{
"scalar_instead_of_struct",
}}
if err := mergo.Merge(&dst, src, mergo.WithSliceDeepMerge); err == nil {
t.Fatal("expected error when merging scalar into struct inside []interface{}, got nil")
}
}
87 changes: 54 additions & 33 deletions merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,50 @@ type Config struct {
overwriteWithEmptyValue bool
overwriteSliceWithEmptyValue bool
sliceDeepCopy bool
sliceDeepMerge bool
retainedCopies []reflect.Value
}

type Transformers interface {
Transformer(reflect.Type) func(dst, src reflect.Value) error
}

func mergeSliceElements(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (reflect.Value, error) {
if src.Type() != dst.Type() {
return reflect.Value{}, fmt.Errorf("cannot deep merge two slices with different type (%s, %s)", src.Type(), dst.Type())
}
for i := 0; i < src.Len() && i < dst.Len(); i++ {
srcElement := src.Index(i)
dstElement := dst.Index(i)
if srcElement.CanInterface() {
srcElement = reflect.ValueOf(srcElement.Interface())
}
isIfaceStruct := dstElement.Kind() == reflect.Interface && !dstElement.IsNil() && dstElement.Elem().Kind() == reflect.Struct
if isIfaceStruct {
if srcElement.IsValid() && srcElement.Type() != dstElement.Elem().Type() {
return reflect.Value{}, fmt.Errorf("cannot override two slices with different type (%s, %s)", srcElement.Type(), dstElement.Elem().Type())
}
copyElem := reflect.New(dstElement.Elem().Type()).Elem()
copyElem.Set(dstElement.Elem())
config.retainedCopies = append(config.retainedCopies, copyElem)
dstElement = copyElem
} else if (!config.sliceDeepMerge || dstElement.Kind() == reflect.Interface) && !(dstElement.Kind() == reflect.Interface && dstElement.IsNil()) && dstElement.CanInterface() {
dstElement = reflect.ValueOf(dstElement.Interface())
}

if err := deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
return reflect.Value{}, err
}
if isIfaceStruct {
dst.Index(i).Set(dstElement)
}
}
if config.sliceDeepMerge && src.Len() > dst.Len() {
dst = reflect.AppendSlice(dst, src.Slice(dst.Len(), src.Len()))
}
return dst, nil
}

// Traverses recursively both values, assigning src's fields values to dst.
// The map argument tracks comparisons that have already been seen, which allows
// short circuiting on recursive types.
Expand All @@ -61,6 +99,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 @@ -161,7 +200,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
dstSlice = reflect.ValueOf(dstElement.Interface())
}

if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy {
if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy && !sliceDeepMerge {
if typeCheck && srcSlice.Type() != dstSlice.Type() {
return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
}
Expand All @@ -171,24 +210,10 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
}
dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
} else if sliceDeepCopy {
i := 0
for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ {
srcElement := srcSlice.Index(i)
dstElement := dstSlice.Index(i)

if srcElement.CanInterface() {
srcElement = reflect.ValueOf(srcElement.Interface())
}
if dstElement.CanInterface() {
dstElement = reflect.ValueOf(dstElement.Interface())
}

if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
return
}
} else if sliceDeepCopy || sliceDeepMerge {
if dstSlice, err = mergeSliceElements(dstSlice, srcSlice, visited, depth, config); err != nil {
return
}

}
dst.SetMapIndex(key, dstSlice)
}
Expand Down Expand Up @@ -224,28 +249,19 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
if !dst.CanSet() {
break
}
if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy {
if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy && !sliceDeepMerge {
dst.Set(src)
} else if config.AppendSlice {
if src.Type() != dst.Type() {
return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type())
}
dst.Set(reflect.AppendSlice(dst, src))
} else if sliceDeepCopy {
for i := 0; i < src.Len() && i < dst.Len(); i++ {
srcElement := src.Index(i)
dstElement := dst.Index(i)
if srcElement.CanInterface() {
srcElement = reflect.ValueOf(srcElement.Interface())
}
if dstElement.CanInterface() {
dstElement = reflect.ValueOf(dstElement.Interface())
}

if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
return
}
} else if sliceDeepCopy || sliceDeepMerge {
var mergedSlice reflect.Value
if mergedSlice, err = mergeSliceElements(dst, src, visited, depth, config); err != nil {
return
}
dst.Set(mergedSlice)
}
case reflect.Pointer:
fallthrough
Expand Down Expand Up @@ -367,6 +383,11 @@ func WithSliceDeepCopy(config *Config) {
config.Overwrite = true
}

// WithSliceDeepMerge will deep-merge overlapping slice elements and append trailing src slice elements.
func WithSliceDeepMerge(config *Config) {
config.sliceDeepMerge = true
}

func merge(dst, src interface{}, opts ...func(*Config)) error {
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Pointer {
return ErrNonPointerArgument
Expand Down