diff --git a/issue115_test.go b/issue115_test.go deleted file mode 100644 index cce1172..0000000 --- a/issue115_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package mergo - -import ( - "testing" -) - -func TestIssue115MergeMapWithNilValueToMapWithOverride(t *testing.T) { - p1 := map[string]interface{}{ - "A": 0, "B": 1, "C": 2, - } - p2 := map[string]interface{}{ - "D": nil, - } - if err := Map(&p1, p2, WithOverride); err != nil { - t.Fatalf("Error during the merge: %v", err) - } - if _, ok := p1["D"]; !ok { - t.Errorf("p1 should contain D: %+v", p1) - } -} diff --git a/issue123_test.go b/issue123_test.go deleted file mode 100644 index 4bfbe15..0000000 --- a/issue123_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package mergo - -import ( - "testing" -) - -func TestIssue123(t *testing.T) { - src := map[string]interface{}{ - "col1": nil, - "col2": 4, - "col3": nil, - } - dst := map[string]interface{}{ - "col1": 2, - "col2": 3, - "col3": 3, - } - - // Expected behavior - if err := Merge(&dst, src, WithOverride); err != nil { - t.Error(err) - } - testCases := []struct { - key string - expected interface{} - }{ - { - "col1", - nil, - }, - { - "col2", - 4, - }, - { - "col3", - nil, - }, - } - for _, tC := range testCases { - if dst[tC.key] != tC.expected { - t.Errorf("expected %v in dst[%q], got %v", tC.expected, tC.key, dst[tC.key]) - } - } -} diff --git a/issue90_test.go b/issue90_test.go deleted file mode 100644 index 45b0504..0000000 --- a/issue90_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package mergo - -import ( - "reflect" - "testing" -) - -type CustomStruct struct { - SomeMap map[string]string -} - -type issue90TestData struct { - name string - src map[string]CustomStruct - dst map[string]CustomStruct - exp map[string]CustomStruct -} - -func issue90Data() []issue90TestData { - return []issue90TestData{ - { - name: "Normal", - dst: map[string]CustomStruct{ - "a": { - SomeMap: map[string]string{ - "key1": "loosethis", "key2": "keepthis", - }, - }, - }, - src: map[string]CustomStruct{ - "a": { - SomeMap: map[string]string{ - "key1": "key10", - }, - }, - }, - exp: map[string]CustomStruct{ - "a": { - SomeMap: map[string]string{ - "key1": "key10", "key2": "keepthis", - }, - }, - }, - }, - { - name: "Init of struct key", - dst: map[string]CustomStruct{ - "a": { - SomeMap: map[string]string{}, - }, - }, - src: map[string]CustomStruct{ - "a": { - SomeMap: map[string]string{ - "key1": "key10", - }, - }, - }, - exp: map[string]CustomStruct{ - "a": { - SomeMap: map[string]string{ - "key1": "key10", - }, - }, - }, - }, - { - name: "Not Init of struct key", - dst: map[string]CustomStruct{}, - src: map[string]CustomStruct{ - "a": { - SomeMap: map[string]string{ - "key1": "key10", - }, - }, - }, - exp: map[string]CustomStruct{ - "a": { - SomeMap: map[string]string{ - "key1": "key10", - }, - }, - }, - }, - { - name: "Nil struct key", - dst: map[string]CustomStruct{ - "a": { - SomeMap: nil, - }, - }, - src: map[string]CustomStruct{ - "a": { - SomeMap: map[string]string{ - "key1": "key10", - }, - }, - }, - exp: map[string]CustomStruct{ - "a": { - SomeMap: map[string]string{ - "key1": "key10", - }, - }, - }, - }, - } -} - -func TestMergoStructMap(t *testing.T) { - for _, data := range issue90Data() { - dst := data.dst - src := data.src - exp := data.exp - - err := Merge(&dst, src, WithAppendSlice, WithOverride) - if err != nil { - t.Errorf("mergo error was not nil, %v", err) - } - - if !reflect.DeepEqual(dst, exp) { - t.Errorf("Actual: %#v did not match \nExpected: %#v", dst, exp) - } - } -} diff --git a/map.go b/map.go index 4823546..a13a7ee 100644 --- a/map.go +++ b/map.go @@ -15,17 +15,11 @@ import ( "unicode/utf8" ) -const ( - hashConstant = 17 -) - func changeInitialCase(s string, mapper func(rune) rune) string { if s == "" { return s } - r, n := utf8.DecodeRuneInString(s) - return string(mapper(r)) + s[n:] } @@ -34,91 +28,16 @@ func isExported(field reflect.StructField) bool { return r >= 'A' && r <= 'Z' } -func mapMap(dst, src reflect.Value, config *Config) { - overwrite := config.Overwrite - dstMap := dst.Interface().(map[string]interface{}) - - for i, n := 0, src.NumField(); i < n; i++ { - srcType := src.Type() - - field := srcType.Field(i) - if !isExported(field) { - continue - } - - fieldName := changeInitialCase(field.Name, unicode.ToLower) - if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v)) || overwrite) { - dstMap[fieldName] = src.Field(i).Interface() - } - } -} - -func mapStruct(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) error { - zeroValue := reflect.Value{} - - srcMap := src.Interface().(map[string]interface{}) - for key := range srcMap { - config.overwriteWithEmptyValue = true - srcValue := srcMap[key] - fieldName := changeInitialCase(key, unicode.ToUpper) - dstElement := dst.FieldByName(fieldName) - - if dstElement == zeroValue { - // We discard it because the field doesn't exist. - continue - } - - srcElement := reflect.ValueOf(srcValue) - dstKind := dstElement.Kind() - srcKind := srcElement.Kind() - - if srcKind == reflect.Ptr && dstKind != reflect.Ptr { - srcElement = srcElement.Elem() - srcKind = reflect.TypeOf(srcElement.Interface()).Kind() - } else if dstKind == reflect.Ptr { - // Can this work? I guess it can't. - if srcKind != reflect.Ptr && srcElement.CanAddr() { - srcPtr := srcElement.Addr() - srcElement = reflect.ValueOf(srcPtr) - srcKind = reflect.Ptr - } - } - - if !srcElement.IsValid() { - continue - } - - depth++ - - switch { - case srcKind == dstKind: - fallthrough - case dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface: - if _, err := deepMerge(dstElement, srcElement, visited, depth, config); err != nil { - return err - } - case srcKind == reflect.Map: - if err := deepMap(dstElement, srcElement, visited, depth, config); err != nil { - return err - } - default: - return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind) - } - } - - return 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. -func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) error { +func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { + overwrite := config.Overwrite if dst.CanAddr() { addr := dst.UnsafeAddr() - h := hashConstant * addr + h := 17 * addr seen := visited[h] typ := dst.Type() - for p := seen; p != nil; p = p.next { if p.ptr == addr && p.typ == typ { return nil @@ -127,24 +46,76 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, conf // Remember, remember... visited[h] = &visit{addr, typ, seen} } - + zeroValue := reflect.Value{} switch dst.Kind() { case reflect.Map: - mapMap(dst, src, config) + dstMap := dst.Interface().(map[string]interface{}) + for i, n := 0, src.NumField(); i < n; i++ { + srcType := src.Type() + field := srcType.Field(i) + if !isExported(field) { + continue + } + fieldName := field.Name + fieldName = changeInitialCase(fieldName, unicode.ToLower) + if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v)) || overwrite) { + dstMap[fieldName] = src.Field(i).Interface() + } + } case reflect.Ptr: if dst.IsNil() { v := reflect.New(dst.Type().Elem()) dst.Set(v) } - dst = dst.Elem() - fallthrough case reflect.Struct: - return mapStruct(dst, src, visited, depth, config) - } + srcMap := src.Interface().(map[string]interface{}) + for key := range srcMap { + config.overwriteWithEmptyValue = true + srcValue := srcMap[key] + fieldName := changeInitialCase(key, unicode.ToUpper) + dstElement := dst.FieldByName(fieldName) + if dstElement == zeroValue { + // We discard it because the field doesn't exist. + continue + } + srcElement := reflect.ValueOf(srcValue) + dstKind := dstElement.Kind() + srcKind := srcElement.Kind() + if srcKind == reflect.Ptr && dstKind != reflect.Ptr { + srcElement = srcElement.Elem() + srcKind = reflect.TypeOf(srcElement.Interface()).Kind() + } else if dstKind == reflect.Ptr { + // Can this work? I guess it can't. + if srcKind != reflect.Ptr && srcElement.CanAddr() { + srcPtr := srcElement.Addr() + srcElement = reflect.ValueOf(srcPtr) + srcKind = reflect.Ptr + } + } - return nil + if !srcElement.IsValid() { + continue + } + if srcKind == dstKind { + if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface { + if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } else if srcKind == reflect.Map { + if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } else { + return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind) + } + } + } + return } // Map sets fields' values in dst from src. @@ -176,8 +147,8 @@ func _map(dst, src interface{}, opts ...func(*Config)) error { var ( vDst, vSrc reflect.Value err error - config = &Config{} ) + config := &Config{} for _, opt := range opts { opt(config) @@ -189,10 +160,8 @@ func _map(dst, src interface{}, opts ...func(*Config)) error { // To be friction-less, we redirect equal-type arguments // to deepMerge. Only because arguments can be anything. if vSrc.Kind() == vDst.Kind() { - _, err := deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) - return err + return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) } - switch vSrc.Kind() { case reflect.Struct: if vDst.Kind() != reflect.Map { @@ -205,6 +174,5 @@ func _map(dst, src interface{}, opts ...func(*Config)) error { default: return ErrNotSupported } - return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config) } diff --git a/merge.go b/merge.go index 21085bd..ac904f2 100644 --- a/merge.go +++ b/merge.go @@ -11,7 +11,6 @@ package mergo import ( "fmt" "reflect" - "unsafe" ) func isMergeableField(dst reflect.Value) (exported bool) { @@ -23,8 +22,7 @@ func isMergeableField(dst reflect.Value) (exported bool) { exported = exported || len(field.PkgPath) == 0 } } - - return false + return } func isExportedComponent(field *reflect.StructField) bool { @@ -43,13 +41,12 @@ type Config struct { Overwrite bool AppendSlice bool TypeCheck bool + Transformers Transformers overwriteWithEmptyValue bool overwriteSliceWithEmptyValue bool sliceDeepCopy bool } -// Transformers allow to merge specific types differently than in the default behavior. -// In other words, now you can customize how some types are merged. type Transformers interface { Transformer(reflect.Type) func(dst, src reflect.Value) error } @@ -57,8 +54,7 @@ type Transformers interface { // 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. -func deepMerge(dstIn, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (dst reflect.Value, err error) { - dst = dstIn +func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { overwrite := config.Overwrite typeCheck := config.TypeCheck overwriteWithEmptySrc := config.overwriteWithEmptyValue @@ -68,19 +64,16 @@ func deepMerge(dstIn, src reflect.Value, visited map[uintptr]*visit, depth int, if !src.IsValid() { return } - if dst.CanAddr() { addr := dst.UnsafeAddr() h := 17 * addr - typ := dst.Type() seen := visited[h] - + typ := dst.Type() for p := seen; p != nil; p = p.next { if p.ptr == addr && p.typ == typ { - return + return nil } } - // Remember, remember... visited[h] = &visit{addr, typ, seen} } @@ -92,112 +85,64 @@ func deepMerge(dstIn, src reflect.Value, visited map[uintptr]*visit, depth int, } } - if typeCheck && dst.IsValid() && src.Type() != dst.Type() { - err = fmt.Errorf("cannot append two different types (%s, %s)", src.Kind(), dst.Kind()) - return - } - switch dst.Kind() { case reflect.Struct: if isMergeableField(dst) { for i, n := 0, dst.NumField(); i < n; i++ { - dstField := dst.Field(i) - structField := dst.Type().Field(i) - // copy un-exported struct fields - if !isExportedComponent(&structField) { - rf := dstCp.Field(i) - rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem() //nolint:gosec - - dstRF := dst.Field(i) - if !dstRF.CanAddr() { - continue - } - - dstRF = reflect.NewAt(dstRF.Type(), unsafe.Pointer(dstRF.UnsafeAddr())).Elem() //nolint:gosec - rf.Set(dstRF) - - continue - } - - dstField, err = deepMerge(dstField, src.Field(i), visited, depth+1, config) - if err != nil { + if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil { return } - - dstCp.Field(i).Set(dstField) } } else { if (isReflectNil(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) { dst.Set(src) } - - return - } - - if (isReflectNil(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) { - dst = src } case reflect.Map: if dst.IsNil() && !src.IsNil() { - if dst.CanSet() { - dst.Set(reflect.MakeMap(dst.Type())) - } else { - dst = src - return - } + dst.Set(reflect.MakeMap(dst.Type())) } - for _, key := range src.MapKeys() { srcElement := src.MapIndex(key) if !srcElement.IsValid() { continue } - dstElement := dst.MapIndex(key) - if dstElement.IsValid() { - k := dstElement.Interface() - dstElement = reflect.ValueOf(k) - } - switch srcElement.Kind() { case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice: - if isReflectNil(srcElement) { - if overwrite || isReflectNil(dstElement) { - dst.SetMapIndex(key, srcElement) - } + if srcElement.IsNil() { continue } - } - - if !srcElement.CanInterface() { - continue - } - - if srcElement.CanInterface() { - srcElement = reflect.ValueOf(srcElement.Interface()) - - if dstElement.IsValid() { - dstElement = reflect.ValueOf(dstElement.Interface()) + fallthrough + default: + if !srcElement.CanInterface() { + continue } - } - - if dstElement.IsValid() && dstElement.Type() == srcElement.Type() { - newSlice := srcElement - if overwrite || overwriteSliceWithEmptySrc || overwriteWithEmptySrc { - if dstElement.Kind() == reflect.Slice && config.AppendSlice { - if dstElement.Type().Elem().Kind() == srcElement.Type().Elem().Kind() { - newSlice = reflect.AppendSlice(dstElement, srcElement) - } else { - err = fmt.Errorf("cannot override two slices with different type (%s, %s)", src.Type(), dst.Type()) - return + switch reflect.TypeOf(srcElement.Interface()).Kind() { + case reflect.Struct: + fallthrough + case reflect.Ptr: + fallthrough + case reflect.Map: + srcMapElm := srcElement + dstMapElm := dstElement + if srcMapElm.CanInterface() { + srcMapElm = reflect.ValueOf(srcMapElm.Interface()) + if dstMapElm.IsValid() { + dstMapElm = reflect.ValueOf(dstMapElm.Interface()) } } - dst.SetMapIndex(key, newSlice) - dstElement = newSlice - } else { - if isEmptyValue(dstElement) { - dst.SetMapIndex(key, newSlice) - dstElement = newSlice + if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil { + return + } + case reflect.Slice: + srcSlice := reflect.ValueOf(srcElement.Interface()) + + var dstSlice reflect.Value + if !dstElement.IsValid() || dstElement.IsNil() { + dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len()) + } else { + dstSlice = reflect.ValueOf(dstElement.Interface()) } if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy { @@ -229,15 +174,19 @@ func deepMerge(dstIn, src reflect.Value, visited map[uintptr]*visit, depth int, } } + dst.SetMapIndex(key, dstSlice) } } - - dstElement, err = deepMerge(dstElement, srcElement, visited, depth+1, config) - if err != nil { - return + if dstElement.IsValid() && !isEmptyValue(dstElement) && (reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map || reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice) { + continue } - dst.SetMapIndex(key, dstElement) + if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement)) { + if dst.IsNil() { + dst.Set(reflect.MakeMap(dst.Type())) + } + dst.SetMapIndex(key, srcElement) + } } case reflect.Slice: if !dst.CanSet() { @@ -246,15 +195,8 @@ func deepMerge(dstIn, src reflect.Value, visited map[uintptr]*visit, depth int, if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy { dst.Set(src) } else if config.AppendSlice { - if typeCheck && src.Type() != dst.Type() { - err = fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type()) - 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.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 { @@ -280,48 +222,12 @@ func deepMerge(dstIn, src reflect.Value, visited map[uintptr]*visit, depth int, break } - 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 + if dst.Kind() != reflect.Ptr && src.Type().AssignableTo(dst.Type()) { + if dst.IsNil() || overwrite { + if dst.CanSet() && (overwrite || isEmptyValue(dst)) { + dst.Set(src) } - - newSlice.Index(i).Set(dstElem) } - } - - if dst.CanSet() { - dst.Set(newSlice) - } else { - dst = newSlice - } - case reflect.Ptr, reflect.Interface: - if isReflectNil(src) { break } @@ -331,52 +237,24 @@ func deepMerge(dstIn, src reflect.Value, visited map[uintptr]*visit, depth int, dst.Set(src) } } else if src.Kind() == reflect.Ptr { - if dst, err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { + if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { return } - dst = dst.Addr() } else if dst.Elem().Type() == src.Type() { - if dst, err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil { + if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil { return } } else { - err = ErrDifferentArgumentsTypes - return + return ErrDifferentArgumentsTypes } - break } - if dst.IsNil() || overwrite { - if (overwrite || isEmptyValue(dst)) && (overwriteWithEmptySrc || !isEmptyValue(src)) { - if dst.CanSet() { - dst.Set(src) - } else { - dst = src - } - } - break - } - - if dst.Elem().Kind() == src.Elem().Kind() { - if dst, err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { - return - } - break - } - - if dst.Kind() != reflect.Ptr && src.Type().AssignableTo(dst.Type()) { - if dst.IsNil() || overwrite { - if overwrite || isEmptyValue(dst) { - if dst.CanSet() { - dst.Set(src) - } else { - dst = src - } - } + if dst.CanSet() && (overwrite || isEmptyValue(dst)) { + dst.Set(src) } - - break + } else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { + return } default: mustSet := (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) @@ -389,7 +267,7 @@ func deepMerge(dstIn, src reflect.Value, visited map[uintptr]*visit, depth int, } } - return dst, nil + return } // Merge will fill any empty for value type attributes on the dst struct using corresponding @@ -432,7 +310,6 @@ func WithOverrideEmptySlice(config *Config) { // WithAppendSlice will make merge append slices instead of overwriting it. func WithAppendSlice(config *Config) { - config.TypeCheck = true config.AppendSlice = true } @@ -465,27 +342,10 @@ func merge(dst, src interface{}, opts ...func(*Config)) error { if vDst, vSrc, err = resolveValues(dst, src); err != nil { return err } - if !vDst.CanSet() { - return fmt.Errorf("cannot set dst, needs reference") - } if vDst.Type() != vSrc.Type() { return ErrDifferentArgumentsTypes } - _, err = deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) - return err -} - -// IsReflectNil is the reflect value provided nil -func isReflectNil(v reflect.Value) bool { - k := v.Kind() - switch k { - case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr: - // Both interface and slice are nil if first word is 0. - // Both are always bigger than a word; assume flagIndir. - return v.IsNil() - default: - return false - } + return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) } // IsReflectNil is the reflect value provided nil diff --git a/merge_interface_concrete_test.go b/merge_interface_concrete_test.go deleted file mode 100644 index 510177c..0000000 --- a/merge_interface_concrete_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package mergo - -import ( - "net/http" - "net/http/httptest" - "testing" -) - -type ifaceTypesTest struct { - N int - Handler http.Handler -} - -type ifaceTypesHandler int - -func (*ifaceTypesHandler) ServeHTTP(rw http.ResponseWriter, _ *http.Request) { - rw.Header().Set("Test", "ifaceTypesHandler") -} - -func TestMergeInterfaceWithDifferentConcreteTypes(t *testing.T) { - dst := ifaceTypesTest{ - Handler: new(ifaceTypesHandler), - } - - src := ifaceTypesTest{ - N: 42, - Handler: http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { - rw.Header().Set("Test", "handlerFunc") - }), - } - - if err := Merge(&dst, src); err != nil { - t.Errorf("Error while merging %s", err) - } - - rw := httptest.NewRecorder() - dst.Handler.ServeHTTP(rw, nil) - - if got, want := rw.Header().Get("Test"), "ifaceTypesHandler"; got != want { - t.Errorf("Handler not merged in properly: got %q header value %q, want %q", "Test", got, want) - } -} - -func TestMergeInterfaceWithSameConcreteTypes(t *testing.T) { - type testStruct struct { - Name string - Value string - } - type interfaceStruct struct { - Field interface{} - } - dst := interfaceStruct{ - Field: testStruct{ - Value: "keepMe", - }, - } - - src := interfaceStruct{ - Field: testStruct{ - Name: t.Name(), - }, - } - - if err := Merge(&dst, src); err != nil { - t.Errorf("Error while merging %s", err) - } - - dstData := dst.Field.(testStruct) - srcData := src.Field.(testStruct) - if dstData.Name != srcData.Name { - t.Errorf("dst name was not updated: got %s, want %s", dstData.Name, srcData.Name) - } - if dstData.Value != "keepMe" { - t.Errorf("dst value was not preserved: got %s, want %s", dstData.Value, "keepMe") - } -} diff --git a/mergo_test.go b/mergo_test.go index 926738c..ca608bb 100644 --- a/mergo_test.go +++ b/mergo_test.go @@ -129,10 +129,10 @@ func TestComplexStruct(t *testing.T) { } func TestComplexStructWithOverwrite(t *testing.T) { - a := complexTest{St: simpleTest{1}, sz: 1, ID: "do-not-overwrite-with-empty-value"} - b := complexTest{St: simpleTest{42}, sz: 2, ID: ""} + a := complexTest{simpleTest{1}, 1, "do-not-overwrite-with-empty-value"} + b := complexTest{simpleTest{42}, 2, ""} - expect := complexTest{St: simpleTest{42}, sz: 1, ID: "do-not-overwrite-with-empty-value"} + expect := complexTest{simpleTest{42}, 1, "do-not-overwrite-with-empty-value"} if err := MergeWithOverwrite(&a, b); err != nil { t.FailNow() } @@ -156,7 +156,7 @@ func TestPointerStruct(t *testing.T) { } type embeddingStruct struct { - A embeddedStruct + embeddedStruct } type embeddedStruct struct { @@ -345,13 +345,13 @@ func TestEmptyToNotEmptyMaps(t *testing.T) { } func TestMapsWithOverwrite(t *testing.T) { - dst := map[string]simpleTest{ + m := map[string]simpleTest{ "a": {}, // overwritten by 16 "b": {42}, // overwritten by 0, as map Value is not addressable and it doesn't check for b is set or not set in `n` "c": {13}, // overwritten by 12 "d": {61}, } - src := map[string]simpleTest{ + n := map[string]simpleTest{ "a": {16}, "b": {}, "c": {12}, @@ -359,7 +359,7 @@ func TestMapsWithOverwrite(t *testing.T) { } expect := map[string]simpleTest{ "a": {16}, - "b": {42}, + "b": {}, "c": {12}, "d": {61}, "e": {14}, @@ -536,13 +536,23 @@ func TestMergeUsingStructAndMap(t *testing.T) { } func TestMaps(t *testing.T) { m := map[string]simpleTest{ - "a": {0}, "b": {42}, "c": {13}, "d": {61}, + "a": {}, + "b": {42}, + "c": {13}, + "d": {61}, } n := map[string]simpleTest{ - "a": {16}, "b": {}, "c": {12}, "e": {14}, + "a": {16}, + "b": {}, + "c": {12}, + "e": {14}, } expect := map[string]simpleTest{ - "a": {16}, "b": {42}, "c": {13}, "d": {61}, "e": {14}, + "a": {0}, + "b": {42}, + "c": {13}, + "d": {61}, + "e": {14}, } if err := Merge(&m, n); err != nil { @@ -893,12 +903,12 @@ func TestMergeMapWithInnerSliceOfDifferentType(t *testing.T) { { "With override and append slice", []func(*Config){WithOverride, WithAppendSlice}, - "cannot append two different types ([]int, []string)", + "cannot append two slices with different type", }, { "With override and type check", []func(*Config){WithOverride, WithTypeCheck}, - "cannot append two different types ([]int, []string)", + "cannot override two slices with different type", }, } for _, tc := range testCases {