From ee2d0e93571f21ec1f4944a3e229ed6c589723b6 Mon Sep 17 00:00:00 2001 From: "timo.huovinen" Date: Mon, 5 Jun 2023 19:04:30 +0200 Subject: [PATCH] fix case-insensitivity code The commit changes the default behavior to be case-insensitive, which breaks tests and compatibility, this fixes that. I've duplicated the tests because it was a bit difficult to do it in any other way --- copier.go | 21 +- copier_benchmark_test.go | 2 +- copier_insensitive_test.go | 1812 ++++++++++++++++++++++++++++++++++++ copier_test.go | 8 +- 4 files changed, 1828 insertions(+), 15 deletions(-) create mode 100644 copier_insensitive_test.go diff --git a/copier.go b/copier.go index bfa8308..7c2a863 100644 --- a/copier.go +++ b/copier.go @@ -37,10 +37,10 @@ const ( type Option struct { // setting this value to true will ignore copying zero values of all the fields, including bools, as well as a // struct having all it's fields set to their zero values respectively (see IsZero() in reflect/value.go) - IgnoreEmpty bool - CaseSensitive bool - DeepCopy bool - Converters []TypeConverter + IgnoreEmpty bool + CaseInsensitive bool + DeepCopy bool + Converters []TypeConverter } func (opt Option) converters() map[converterPair]TypeConverter { @@ -329,7 +329,7 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) break } - toField := fieldByName(dest, destFieldName, opt.CaseSensitive) + toField := fieldByName(dest, destFieldName, opt.CaseInsensitive) if toField.IsValid() { if toField.CanSet() { isSet, err := set(toField, fromField, opt.DeepCopy, converters) @@ -375,7 +375,7 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) } if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, opt.IgnoreEmpty) { - if toField := fieldByName(dest, destFieldName, opt.CaseSensitive); toField.IsValid() && toField.CanSet() { + if toField := fieldByName(dest, destFieldName, opt.CaseInsensitive); toField.IsValid() && toField.CanSet() { values := fromMethod.Call([]reflect.Value{}) if len(values) >= 1 { set(toField, values[0], opt.DeepCopy, converters) @@ -771,10 +771,9 @@ func driverValuer(v reflect.Value) (i driver.Valuer, ok bool) { return } -func fieldByName(v reflect.Value, name string, caseSensitive bool) reflect.Value { - if caseSensitive { - return v.FieldByName(name) +func fieldByName(v reflect.Value, name string, caseInsensitive bool) reflect.Value { + if caseInsensitive { + return v.FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, name) }) } - - return v.FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, name) }) + return v.FieldByName(name) } diff --git a/copier_benchmark_test.go b/copier_benchmark_test.go index d9daccc..a9a25d8 100644 --- a/copier_benchmark_test.go +++ b/copier_benchmark_test.go @@ -29,7 +29,7 @@ func BenchmarkNamaCopy(b *testing.B) { for x := 0; x < b.N; x++ { employee := &Employee{ Name: user.Name, - NickName: &user.Nickname, + Nickname: &user.Nickname, Age: int64(user.Age), FakeAge: int(*user.FakeAge), DoubleAge: user.DoubleAge(), diff --git a/copier_insensitive_test.go b/copier_insensitive_test.go new file mode 100644 index 0000000..d9cfaa7 --- /dev/null +++ b/copier_insensitive_test.go @@ -0,0 +1,1812 @@ +package copier_test + +import ( + "database/sql" + "errors" + "fmt" + "reflect" + "testing" + "time" + + "github.com/jinzhu/copier" +) + +type UserInsensitive struct { + Name string + Birthday *time.Time + NickName string + Role string + Age int32 + FakeAge *int32 + Notes []string + flags []byte +} + +func (user UserInsensitive) DoubleAge() int32 { + return 2 * user.Age +} + +type EmployeeInsensitive struct { + _User *User + Name string + Birthday *time.Time + NickName *string + Age int64 + FakeAge int + EmployeID int64 + DoubleAge int32 + SuperRule string + Notes []*string + flags []byte +} + +func (employee *EmployeeInsensitive) Role(role string) { + employee.SuperRule = "Super " + role +} + +func checkEmployeeInsensitive(employee Employee, user User, t *testing.T, testCase string) { + t.Helper() + if employee.Name != user.Name { + t.Errorf("%v: Name haven't been copied correctly.", testCase) + } + if employee.Nickname == nil || *employee.Nickname != user.Nickname { + t.Errorf("%v: NickName haven't been copied correctly.", testCase) + } + if employee.Birthday == nil && user.Birthday != nil { + t.Errorf("%v: Birthday haven't been copied correctly.", testCase) + } + if employee.Birthday != nil && user.Birthday == nil { + t.Errorf("%v: Birthday haven't been copied correctly.", testCase) + } + if employee.Birthday != nil && user.Birthday != nil && + !employee.Birthday.Equal(*(user.Birthday)) { + t.Errorf("%v: Birthday haven't been copied correctly.", testCase) + } + if employee.Age != int64(user.Age) { + t.Errorf("%v: Age haven't been copied correctly.", testCase) + } + if user.FakeAge != nil && employee.FakeAge != int(*user.FakeAge) { + t.Errorf("%v: FakeAge haven't been copied correctly.", testCase) + } + if employee.DoubleAge != user.DoubleAge() { + t.Errorf("%v: Copy from method doesn't work", testCase) + } + if employee.SuperRule != "Super "+user.Role { + t.Errorf("%v: Copy to method doesn't work", testCase) + } + + if len(employee.Notes) != len(user.Notes) { + t.Fatalf("%v: Copy from slice doesn't work, employee notes len: %v, user: %v", testCase, len(employee.Notes), len(user.Notes)) + } + + for idx, note := range user.Notes { + if note != *employee.Notes[idx] { + t.Fatalf("%v: Copy from slice doesn't work, notes idx: %v employee: %v user: %v", testCase, idx, *employee.Notes[idx], note) + } + } +} + +func TestCopySameStructWithPointerFieldInsensitive(t *testing.T) { + optionsInsensitive := copier.Option{ + CaseInsensitive: true, + } + var fakeAge int32 = 12 + var currentTime time.Time = time.Now() + user := &User{Birthday: ¤tTime, Name: "Jinzhu", Nickname: "jinzhu", Age: 18, FakeAge: &fakeAge, Role: "Admin", Notes: []string{"hello world", "welcome"}, flags: []byte{'x'}} + newUser := &User{} + copier.CopyWithOption(newUser, user, optionsInsensitive) + if user.Birthday == newUser.Birthday { + t.Errorf("TestCopySameStructWithPointerField: copy Birthday failed since they need to have different address") + } + + if user.FakeAge == newUser.FakeAge { + t.Errorf("TestCopySameStructWithPointerField: copy FakeAge failed since they need to have different address") + } +} + +func checkEmployee2Insensitive(employee Employee, user *User, t *testing.T, testCase string) { + t.Helper() + if user == nil { + if employee.Name != "" || employee.Nickname != nil || employee.Birthday != nil || employee.Age != 0 || + employee.DoubleAge != 0 || employee.FakeAge != 0 || employee.SuperRule != "" || employee.Notes != nil { + t.Errorf("%v : employee should be empty", testCase) + } + return + } + + checkEmployeeInsensitive(employee, *user, t, testCase) +} + +func TestCopyStructInsensitive(t *testing.T) { + optionsInsensitive := copier.Option{ + CaseInsensitive: true, + } + var fakeAge int32 = 12 + user := User{Name: "Jinzhu", Nickname: "jinzhu", Age: 18, FakeAge: &fakeAge, Role: "Admin", Notes: []string{"hello world", "welcome"}, flags: []byte{'x'}} + employee := Employee{} + + if err := copier.CopyWithOption(employee, &user, optionsInsensitive); err == nil { + t.Errorf("Copy to unaddressable value should get error") + } + + copier.CopyWithOption(&employee, &user, optionsInsensitive) + checkEmployeeInsensitive(employee, user, t, "Copy From Ptr To Ptr") + + employee2 := Employee{} + copier.CopyWithOption(&employee2, user, optionsInsensitive) + checkEmployeeInsensitive(employee2, user, t, "Copy From Struct To Ptr") + + employee3 := Employee{} + ptrToUser := &user + copier.CopyWithOption(&employee3, &ptrToUser, optionsInsensitive) + checkEmployeeInsensitive(employee3, user, t, "Copy From Double Ptr To Ptr") + + employee4 := &Employee{} + copier.CopyWithOption(&employee4, user, optionsInsensitive) + checkEmployeeInsensitive(*employee4, user, t, "Copy From Ptr To Double Ptr") + + employee5 := &Employee{} + copier.CopyWithOption(&employee5, &employee, optionsInsensitive) + checkEmployeeInsensitive(*employee5, user, t, "Copy From Employee To Employee") +} + +func TestCopyFromStructToSliceInsensitive(t *testing.T) { + user := User{Name: "Jinzhu", Age: 18, Role: "Admin", Notes: []string{"hello world"}} + employees := []Employee{} + optionsInsensitive := copier.Option{ + CaseInsensitive: true, + } + + if err := copier.CopyWithOption(employees, &user, optionsInsensitive); err != nil && len(employees) != 0 { + t.Errorf("Copy to unaddressable value should get error") + } + + if copier.CopyWithOption(&employees, &user, optionsInsensitive); len(employees) != 1 { + t.Errorf("Should only have one elem when copy struct to slice") + } else { + checkEmployeeInsensitive(employees[0], user, t, "Copy From Struct To Slice Ptr") + } + + employees2 := &[]Employee{} + if copier.CopyWithOption(&employees2, user, optionsInsensitive); len(*employees2) != 1 { + t.Errorf("Should only have one elem when copy struct to slice") + } else { + checkEmployeeInsensitive((*employees2)[0], user, t, "Copy From Struct To Double Slice Ptr") + } + + employees3 := []*Employee{} + if copier.CopyWithOption(&employees3, user, optionsInsensitive); len(employees3) != 1 { + t.Errorf("Should only have one elem when copy struct to slice") + } else { + checkEmployeeInsensitive(*(employees3[0]), user, t, "Copy From Struct To Ptr Slice Ptr") + } + + employees4 := &[]*Employee{} + if copier.CopyWithOption(&employees4, user, optionsInsensitive); len(*employees4) != 1 { + t.Errorf("Should only have one elem when copy struct to slice") + } else { + checkEmployeeInsensitive(*((*employees4)[0]), user, t, "Copy From Struct To Double Ptr Slice Ptr") + } +} + +func TestCopyFromSliceToSliceInsensitive(t *testing.T) { + users := []User{ + {Name: "Jinzhu", Age: 18, Role: "Admin", Notes: []string{"hello world"}}, + {Name: "Jinzhu2", Age: 22, Role: "Dev", Notes: []string{"hello world", "hello"}}} + employees := []Employee{} + optionsInsensitive := copier.Option{ + CaseInsensitive: true, + } + + if copier.CopyWithOption(&employees, users, optionsInsensitive); len(employees) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployeeInsensitive(employees[0], users[0], t, "Copy From Slice To Slice Ptr @ 1") + checkEmployeeInsensitive(employees[1], users[1], t, "Copy From Slice To Slice Ptr @ 2") + } + + employees2 := &[]Employee{} + if copier.CopyWithOption(&employees2, &users, optionsInsensitive); len(*employees2) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployeeInsensitive((*employees2)[0], users[0], t, "Copy From Slice Ptr To Double Slice Ptr @ 1") + checkEmployeeInsensitive((*employees2)[1], users[1], t, "Copy From Slice Ptr To Double Slice Ptr @ 2") + } + + employees3 := []*Employee{} + if copier.CopyWithOption(&employees3, users, optionsInsensitive); len(employees3) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployeeInsensitive(*(employees3[0]), users[0], t, "Copy From Slice To Ptr Slice Ptr @ 1") + checkEmployeeInsensitive(*(employees3[1]), users[1], t, "Copy From Slice To Ptr Slice Ptr @ 2") + } + + employees4 := &[]*Employee{} + if copier.CopyWithOption(&employees4, users, optionsInsensitive); len(*employees4) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployeeInsensitive(*((*employees4)[0]), users[0], t, "Copy From Slice Ptr To Double Ptr Slice Ptr @ 1") + checkEmployeeInsensitive(*((*employees4)[1]), users[1], t, "Copy From Slice Ptr To Double Ptr Slice Ptr @ 2") + } +} + +func TestCopyFromSliceToSlice2Insensitive(t *testing.T) { + users := []*User{{Name: "Jinzhu", Age: 18, Role: "Admin", Notes: []string{"hello world"}}, nil} + employees := []Employee{} + optionsInsensitive := copier.Option{ + CaseInsensitive: true, + } + + if copier.CopyWithOption(&employees, users, optionsInsensitive); len(employees) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployee2(employees[0], users[0], t, "Copy From Slice To Slice Ptr @ 1") + checkEmployee2(employees[1], users[1], t, "Copy From Slice To Slice Ptr @ 2") + } + + employees2 := &[]Employee{} + if copier.CopyWithOption(&employees2, &users, optionsInsensitive); len(*employees2) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployee2((*employees2)[0], users[0], t, "Copy From Slice Ptr To Double Slice Ptr @ 1") + checkEmployee2((*employees2)[1], users[1], t, "Copy From Slice Ptr To Double Slice Ptr @ 2") + } + + employees3 := []*Employee{} + if copier.CopyWithOption(&employees3, users, optionsInsensitive); len(employees3) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployee2(*(employees3[0]), users[0], t, "Copy From Slice To Ptr Slice Ptr @ 1") + checkEmployee2(*(employees3[1]), users[1], t, "Copy From Slice To Ptr Slice Ptr @ 2") + } + + employees4 := &[]*Employee{} + if copier.CopyWithOption(&employees4, users, optionsInsensitive); len(*employees4) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployee2(*((*employees4)[0]), users[0], t, "Copy From Slice Ptr To Double Ptr Slice Ptr @ 1") + checkEmployee2(*((*employees4)[1]), users[1], t, "Copy From Slice Ptr To Double Ptr Slice Ptr @ 2") + } +} + +func TestCopyFromSliceToSlice3Insensitive(t *testing.T) { + type CollectionAlias struct { + CollectionName string `json:"collection_name"` + Name string `json:"name"` + } + + optionsInsensitive := copier.Option{ + CaseInsensitive: true, + } + + expectedResult := []*CollectionAlias{ + {"collection", "collection_alias1"}, + {"collection", "collection_alias2"}, + {"collection", "collection_alias3"}, + } + + mockedResult := []*CollectionAlias{} + copier.CopyWithOption(&mockedResult, &expectedResult, optionsInsensitive) + + if len(mockedResult) != len(expectedResult) { + t.Fatalf("failed to copy results") + } + + for idx := range mockedResult { + if mockedResult[idx].Name != mockedResult[idx].Name || mockedResult[idx].CollectionName != mockedResult[idx].CollectionName { + t.Fatalf("failed to copy results") + } + } +} + +func TestEmbeddedAndBaseInsensitive(t *testing.T) { + type Base struct { + BaseField1 int + BaseField2 int + User *User + } + + type Embed struct { + EmbedField1 int + EmbedField2 int + Base + } + + optionsInsensitive := copier.Option{ + CaseInsensitive: true, + } + + base := Base{} + embedded := Embed{} + embedded.BaseField1 = 1 + embedded.BaseField2 = 2 + embedded.EmbedField1 = 3 + embedded.EmbedField2 = 4 + + user := User{ + Name: "testName", + } + embedded.User = &user + + copier.CopyWithOption(&base, &embedded, optionsInsensitive) + + if base.BaseField1 != 1 || base.User.Name != "testName" { + t.Error("Embedded fields not copied") + } + + base.BaseField1 = 11 + base.BaseField2 = 12 + user1 := User{ + Name: "testName1", + } + base.User = &user1 + + copier.CopyWithOption(&embedded, &base, optionsInsensitive) + if embedded.BaseField1 != 11 || embedded.User.Name != "testName1" { + t.Error("base fields not copied") + } +} + +func TestStructFieldInsensitive(t *testing.T) { + type Detail struct { + Info1 string + Info2 *string + } + + type SimilarDetail struct { + Info1 string + Info2 *string + } + + type UserWithDetailsPtr struct { + Details []*Detail + Detail *Detail + Notes *[]string + Notes2 *[]string + } + type UserWithDetails struct { + Details []Detail + Detail Detail + Notes []string + Notes2 []string + } + type UserWithSimilarDetailsPtr struct { + Detail *SimilarDetail + } + type UserWithSimilarDetails struct { + Detail SimilarDetail + } + type EmployeeWithDetails struct { + Detail Detail + } + type EmployeeWithDetailsPtr struct { + Detail *Detail + } + type EmployeeWithSimilarDetails struct { + Detail SimilarDetail + } + type EmployeeWithSimilarDetailsPtr struct { + Detail *SimilarDetail + } + + optionsInsensitive := copier.Option{ + CaseInsensitive: true, + } + + optionsInsensitiveDeepCopy := copier.Option{ + CaseInsensitive: true, + DeepCopy: true, + } + + checkDetail := func(t *testing.T, source Detail, target Detail) { + if source.Info1 != target.Info1 { + t.Errorf("info1 is diff: source: %v, target: %v", source.Info1, target.Info1) + } + + if (source.Info2 != nil || target.Info2 != nil) && (*source.Info2 != *target.Info2) { + t.Errorf("info2 is diff: source: %v, target: %v", *source.Info2, *target.Info2) + } + } + + t.Run("Should work without deepCopy", func(t *testing.T) { + t.Run("Should work with same type and both ptr field", func(t *testing.T) { + info2 := "world" + from := UserWithDetailsPtr{ + Detail: &Detail{Info1: "hello", Info2: &info2}, + Details: []*Detail{{Info1: "hello", Info2: &info2}}, + } + to := UserWithDetailsPtr{} + copier.CopyWithOption(&to, from, optionsInsensitive) + + checkDetail(t, *from.Detail, *to.Detail) + + *to.Detail.Info2 = "new value" + if *from.Detail.Info2 != *to.Detail.Info2 { + t.Fatalf("DeepCopy not enabled") + } + + if len(from.Details) != len(to.Details) { + t.Fatalf("slice should be copied") + } + + for idx, detail := range from.Details { + checkDetail(t, *detail, *to.Details[idx]) + } + }) + + t.Run("Should work with same type and both not ptr field", func(t *testing.T) { + info2 := "world" + from := UserWithDetails{ + Detail: Detail{Info1: "hello", Info2: &info2}, + Details: []Detail{{Info1: "hello", Info2: &info2}}, + } + to := UserWithDetails{} + copier.CopyWithOption(&to, from, optionsInsensitive) + + checkDetail(t, from.Detail, to.Detail) + + *to.Detail.Info2 = "new value" + if *from.Detail.Info2 != *to.Detail.Info2 { + t.Fatalf("DeepCopy not enabled") + } + + if len(from.Details) != len(to.Details) { + t.Fatalf("slice should be copied") + } + + for idx, detail := range from.Details { + checkDetail(t, detail, to.Details[idx]) + } + }) + + t.Run("Should work with different type and both ptr field", func(t *testing.T) { + info2 := "world" + from := UserWithDetailsPtr{Detail: &Detail{Info1: "hello", Info2: &info2}} + to := EmployeeWithDetailsPtr{} + copier.CopyWithOption(&to, from, optionsInsensitive) + + newValue := "new value" + to.Detail.Info2 = &newValue + + if to.Detail.Info1 == "" { + t.Errorf("should not be empty") + } + if to.Detail.Info1 != from.Detail.Info1 { + t.Errorf("should be the same") + } + if to.Detail.Info2 == from.Detail.Info2 { + t.Errorf("should be different") + } + }) + + t.Run("Should work with different type and both not ptr field", func(t *testing.T) { + info2 := "world" + from := UserWithDetails{Detail: Detail{Info1: "hello", Info2: &info2}} + to := EmployeeWithDetails{} + copier.CopyWithOption(&to, from, optionsInsensitive) + + newValue := "new value" + to.Detail.Info2 = &newValue + + if to.Detail.Info1 == "" { + t.Errorf("should not be empty") + } + if to.Detail.Info1 != from.Detail.Info1 { + t.Errorf("should be the same") + } + if to.Detail.Info2 == from.Detail.Info2 { + t.Errorf("should be different") + } + }) + + t.Run("Should work with from ptr field and to not ptr field", func(t *testing.T) { + info2 := "world" + from := UserWithDetailsPtr{Detail: &Detail{Info1: "hello", Info2: &info2}} + to := EmployeeWithDetails{} + copier.CopyWithOption(&to, from, optionsInsensitive) + + newValue := "new value" + to.Detail.Info2 = &newValue + + if to.Detail.Info1 == "" { + t.Errorf("should not be empty") + } + if to.Detail.Info1 != from.Detail.Info1 { + t.Errorf("should be the same") + } + if to.Detail.Info2 == from.Detail.Info2 { + t.Errorf("should be different") + } + }) + + t.Run("Should work with from not ptr field and to ptr field", func(t *testing.T) { + info2 := "world" + from := UserWithDetails{Detail: Detail{Info1: "hello", Info2: &info2}} + to := EmployeeWithDetailsPtr{} + copier.CopyWithOption(&to, from, optionsInsensitive) + + newValue := "new value" + to.Detail.Info2 = &newValue + + if to.Detail.Info1 == "" { + t.Errorf("should not be empty") + } + if to.Detail.Info1 != from.Detail.Info1 { + t.Errorf("should be the same") + } + if to.Detail.Info2 == from.Detail.Info2 { + t.Errorf("should be different") + } + }) + + t.Run("Should work with from a nil ptr slice field to a slice field", func(t *testing.T) { + notes := []string{"hello", "world"} + from := UserWithDetailsPtr{Notes: ¬es, Notes2: nil} + to := UserWithDetails{} + err := copier.CopyWithOption(&to, from, optionsInsensitive) + if err != nil { + t.Errorf("should not return an error") + return + } + + if len(to.Notes) != len(*from.Notes) { + t.Errorf("should be the same length") + } + if to.Notes[0] != (*from.Notes)[0] { + t.Errorf("should be the same") + } + if to.Notes[1] != (*from.Notes)[1] { + t.Errorf("should be the same") + } + }) + }) + + t.Run("Should work with deepCopy", func(t *testing.T) { + t.Run("Should work with same type and both ptr field", func(t *testing.T) { + info2 := "world" + from := UserWithDetailsPtr{ + Detail: &Detail{Info1: "hello", Info2: &info2}, + Details: []*Detail{{Info1: "hello", Info2: &info2}}, + } + to := UserWithDetailsPtr{} + copier.CopyWithOption(&to, from, optionsInsensitiveDeepCopy) + + checkDetail(t, *from.Detail, *to.Detail) + + *to.Detail.Info2 = "new value" + if *from.Detail.Info2 == *to.Detail.Info2 { + t.Fatalf("DeepCopy enabled") + } + + if len(from.Details) != len(to.Details) { + t.Fatalf("slice should be copied") + } + + for idx, detail := range from.Details { + checkDetail(t, *detail, *to.Details[idx]) + } + }) + t.Run("Should work with same type and both not ptr field", func(t *testing.T) { + info2 := "world" + from := UserWithDetails{ + Detail: Detail{Info1: "hello", Info2: &info2}, + Details: []Detail{{Info1: "hello", Info2: &info2}}, + } + to := UserWithDetails{} + copier.CopyWithOption(&to, from, optionsInsensitiveDeepCopy) + + checkDetail(t, from.Detail, to.Detail) + + *to.Detail.Info2 = "new value" + if *from.Detail.Info2 == *to.Detail.Info2 { + t.Fatalf("DeepCopy enabled") + } + + if len(from.Details) != len(to.Details) { + t.Fatalf("slice should be copied") + } + + for idx, detail := range from.Details { + checkDetail(t, detail, to.Details[idx]) + } + }) + + t.Run("Should work with different type and both ptr field", func(t *testing.T) { + info2 := "world" + from := UserWithDetailsPtr{Detail: &Detail{Info1: "hello", Info2: &info2}} + to := EmployeeWithDetailsPtr{} + copier.CopyWithOption(&to, from, optionsInsensitiveDeepCopy) + + newValue := "new value" + to.Detail.Info2 = &newValue + + if to.Detail.Info1 == "" { + t.Errorf("should not be empty") + } + if to.Detail.Info1 != from.Detail.Info1 { + t.Errorf("should be the same") + } + if to.Detail.Info2 == from.Detail.Info2 { + t.Errorf("should be different") + } + }) + + t.Run("Should work with different type and both not ptr field", func(t *testing.T) { + info2 := "world" + from := UserWithDetails{Detail: Detail{Info1: "hello", Info2: &info2}} + to := EmployeeWithDetails{} + copier.CopyWithOption(&to, from, optionsInsensitiveDeepCopy) + + newValue := "new value" + to.Detail.Info2 = &newValue + + if to.Detail.Info1 == "" { + t.Errorf("should not be empty") + } + if to.Detail.Info1 != from.Detail.Info1 { + t.Errorf("should be the same") + } + if to.Detail.Info2 == from.Detail.Info2 { + t.Errorf("should be different") + } + }) + + t.Run("Should work with from ptr field and to not ptr field", func(t *testing.T) { + info2 := "world" + from := UserWithDetailsPtr{Detail: &Detail{Info1: "hello", Info2: &info2}} + to := EmployeeWithDetails{} + copier.CopyWithOption(&to, from, optionsInsensitiveDeepCopy) + + newValue := "new value" + to.Detail.Info2 = &newValue + + if to.Detail.Info1 == "" { + t.Errorf("should not be empty") + } + if to.Detail.Info1 != from.Detail.Info1 { + t.Errorf("should be the same") + } + if to.Detail.Info2 == from.Detail.Info2 { + t.Errorf("should be different") + } + }) + + t.Run("Should work with from not ptr field and to ptr field", func(t *testing.T) { + info2 := "world" + from := UserWithDetails{Detail: Detail{Info1: "hello", Info2: &info2}} + to := EmployeeWithDetailsPtr{} + copier.CopyWithOption(&to, from, optionsInsensitiveDeepCopy) + + newValue := "new value" + to.Detail.Info2 = &newValue + + if to.Detail.Info1 == "" { + t.Errorf("should not be empty") + } + if to.Detail.Info1 != from.Detail.Info1 { + t.Errorf("should be the same") + } + if to.Detail.Info2 == from.Detail.Info2 { + t.Errorf("should be different") + } + }) + + t.Run("Should work with from a nil ptr slice field to a slice field", func(t *testing.T) { + notes := []string{"hello", "world"} + from := UserWithDetailsPtr{Notes: ¬es, Notes2: nil} + to := UserWithDetails{} + err := copier.CopyWithOption(&to, from, optionsInsensitiveDeepCopy) + if err != nil { + t.Errorf("should not return an error") + return + } + + if len(to.Notes) != len(*from.Notes) { + t.Errorf("should be the same length") + } + if to.Notes[0] != (*from.Notes)[0] { + t.Errorf("should be the same") + } + if to.Notes[1] != (*from.Notes)[1] { + t.Errorf("should be the same") + } + + newValue := []string{"new", "value"} + to.Notes = newValue + + if to.Notes[0] == (*from.Notes)[0] { + t.Errorf("should be different") + } + if to.Notes[1] == (*from.Notes)[1] { + t.Errorf("should be different") + } + }) + }) +} + +func TestMapInterfaceInsensitive(t *testing.T) { + type Inner struct { + IntPtr *int + unexportedField string + } + + type Outer struct { + Inner Inner + } + + type DriverOptions struct { + GenOptions map[string]interface{} + } + + var optionsInsensitive = copier.Option{CaseInsensitive: true} + + t.Run("Should work without deepCopy", func(t *testing.T) { + intVal := 5 + outer := Outer{ + Inner: Inner{ + IntPtr: &intVal, + unexportedField: "hello", + }, + } + from := DriverOptions{ + GenOptions: map[string]interface{}{ + "key": outer, + }, + } + to := DriverOptions{} + if err := copier.CopyWithOption(&to, &from, optionsInsensitive); nil != err { + t.Errorf("Unexpected error: %v", err) + return + } + + *to.GenOptions["key"].(Outer).Inner.IntPtr = 6 + + if to.GenOptions["key"].(Outer).Inner.IntPtr != from.GenOptions["key"].(Outer).Inner.IntPtr { + t.Errorf("should be the same") + } + }) + + t.Run("Should work with deepCopy", func(t *testing.T) { + intVal := 5 + outer := Outer{ + Inner: Inner{ + IntPtr: &intVal, + unexportedField: "Hello", + }, + } + from := DriverOptions{ + GenOptions: map[string]interface{}{ + "key": outer, + }, + } + to := DriverOptions{} + if err := copier.CopyWithOption(&to, &from, copier.Option{ + DeepCopy: true, + }); nil != err { + t.Errorf("Unexpected error: %v", err) + return + } + + *to.GenOptions["key"].(Outer).Inner.IntPtr = 6 + + if to.GenOptions["key"].(Outer).Inner.IntPtr == from.GenOptions["key"].(Outer).Inner.IntPtr { + t.Errorf("should be different") + } + }) + + t.Run("Test copy map with nil interface", func(t *testing.T) { + from := map[string]interface{}{"eventId": nil} + to := map[string]interface{}{"eventId": nil} + copier.CopyWithOption(&to, &from, copier.Option{IgnoreEmpty: true, DeepCopy: true}) + if v, ok := to["eventId"]; !ok || v != nil { + t.Errorf("failed to deep copy map with nil, got %v", v) + } + + from["eventId"] = 1 + if v, ok := to["eventId"]; !ok || v != nil { + t.Errorf("failed to deep copy map with nil, got %v", v) + } + + copier.CopyWithOption(&to, &from, copier.Option{IgnoreEmpty: true, DeepCopy: true}) + if v, ok := to["eventId"]; !ok || v != 1 { + t.Errorf("failed to deep copy map with nil") + } + + from["eventId"] = 2 + if v, ok := to["eventId"]; !ok || v != 1 { + t.Errorf("failed to deep copy map with nil") + } + }) + + t.Run("Test copy map with nested slice map", func(t *testing.T) { + var out map[string]interface{} + value := map[string]interface{}{ + "list": []map[string]interface{}{ + { + "shop_id": 123, + }, + }, + "list2": []interface{}{ + map[string]interface{}{ + "shop_id": 123, + }, + }, + } + err := copier.CopyWithOption(&out, &value, copier.Option{IgnoreEmpty: false, DeepCopy: true}) + if err != nil { + t.Fatalf("failed to deep copy nested map") + } + if fmt.Sprintf("%v", out) != fmt.Sprintf("%v", value) { + t.Fatalf("failed to deep copy nested map") + } + }) +} + +func TestInterfaceInsensitive(t *testing.T) { + type Inner struct { + IntPtr *int + } + + type Outer struct { + Inner Inner + } + + type DriverOptions struct { + GenOptions interface{} + } + + var optionsInsensitive = copier.Option{CaseInsensitive: true} + + t.Run("Should work without deepCopy", func(t *testing.T) { + intVal := 5 + outer := Outer{ + Inner: Inner{ + IntPtr: &intVal, + }, + } + from := DriverOptions{ + GenOptions: outer, + } + to := DriverOptions{} + if err := copier.CopyWithOption(&to, from, optionsInsensitive); nil != err { + t.Errorf("Unexpected error: %v", err) + return + } + + *to.GenOptions.(Outer).Inner.IntPtr = 6 + + if to.GenOptions.(Outer).Inner.IntPtr != from.GenOptions.(Outer).Inner.IntPtr { + t.Errorf("should be the same") + } + }) + + t.Run("Should work with deepCopy", func(t *testing.T) { + intVal := 5 + outer := Outer{ + Inner: Inner{ + IntPtr: &intVal, + }, + } + from := DriverOptions{ + GenOptions: outer, + } + to := DriverOptions{} + if err := copier.CopyWithOption(&to, &from, copier.Option{ + DeepCopy: true, + }); nil != err { + t.Errorf("Unexpected error: %v", err) + return + } + + *to.GenOptions.(Outer).Inner.IntPtr = 6 + + if to.GenOptions.(Outer).Inner.IntPtr == from.GenOptions.(Outer).Inner.IntPtr { + t.Errorf("should be different") + } + }) +} + +func TestSliceInsensitive(t *testing.T) { + type ElemOption struct { + Value int + } + + type A struct { + X []int + Options []ElemOption + } + + type B struct { + X []int + Options []ElemOption + } + + var optionsInsensitive = copier.Option{CaseInsensitive: true} + + t.Run("Should work with simple slice", func(t *testing.T) { + from := []int{1, 2} + var to []int + + if err := copier.CopyWithOption(&to, from, optionsInsensitive); nil != err { + t.Errorf("Unexpected error: %v", err) + return + } + + from[0] = 3 + from[1] = 4 + + if to[0] == from[0] { + t.Errorf("should be different") + } + + if len(to) != len(from) { + t.Errorf("should be the same length, got len(from): %v, len(to): %v", len(from), len(to)) + } + }) + + t.Run("Should work with empty slice", func(t *testing.T) { + from := []int{} + to := []int{} + + if err := copier.CopyWithOption(&to, from, optionsInsensitive); nil != err { + t.Errorf("Unexpected error: %v", err) + return + } + + if to == nil { + t.Errorf("should be not nil") + } + }) + + t.Run("Should work without deepCopy", func(t *testing.T) { + x := []int{1, 2} + options := []ElemOption{ + {Value: 10}, + {Value: 20}, + } + from := A{X: x, Options: options} + to := B{} + + if err := copier.CopyWithOption(&to, from, optionsInsensitive); nil != err { + t.Errorf("Unexpected error: %v", err) + return + } + + from.X[0] = 3 + from.X[1] = 4 + from.Options[0].Value = 30 + from.Options[1].Value = 40 + + if to.X[0] != from.X[0] { + t.Errorf("should be the same") + } + + if len(to.X) != len(from.X) { + t.Errorf("should be the same length, got len(from.X): %v, len(to.X): %v", len(from.X), len(to.X)) + } + + if to.Options[0].Value != from.Options[0].Value { + t.Errorf("should be the same") + } + + if to.Options[0].Value != from.Options[0].Value { + t.Errorf("should be the same") + } + + if len(to.Options) != len(from.Options) { + t.Errorf("should be the same") + } + }) + + t.Run("Should work with deepCopy", func(t *testing.T) { + x := []int{1, 2} + options := []ElemOption{ + {Value: 10}, + {Value: 20}, + } + from := A{X: x, Options: options} + to := B{} + + if err := copier.CopyWithOption(&to, from, copier.Option{ + DeepCopy: true, + }); nil != err { + t.Errorf("Unexpected error: %v", err) + return + } + + from.X[0] = 3 + from.X[1] = 4 + from.Options[0].Value = 30 + from.Options[1].Value = 40 + + if to.X[0] == from.X[0] { + t.Errorf("should be different") + } + + if len(to.X) != len(from.X) { + t.Errorf("should be the same length, got len(from.X): %v, len(to.X): %v", len(from.X), len(to.X)) + } + + if to.Options[0].Value == from.Options[0].Value { + t.Errorf("should be different") + } + + if len(to.Options) != len(from.Options) { + t.Errorf("should be the same") + } + }) +} + +func TestAnonymousFieldsInsensitive(t *testing.T) { + t.Run("Should work with unexported ptr fields", func(t *testing.T) { + type nested struct { + A string + } + type parentA struct { + *nested + } + type parentB struct { + *nested + } + + from := parentA{nested: &nested{A: "a"}} + to := parentB{} + + err := copier.CopyWithOption(&to, &from, copier.Option{ + DeepCopy: true, + }) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + from.nested.A = "b" + + if to.nested != nil { + t.Errorf("should be nil") + } + }) + t.Run("Should work with unexported fields", func(t *testing.T) { + type nested struct { + A string + } + type parentA struct { + nested + } + type parentB struct { + nested + } + + from := parentA{nested: nested{A: "a"}} + to := parentB{} + + err := copier.CopyWithOption(&to, &from, copier.Option{ + DeepCopy: true, + }) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + from.nested.A = "b" + + if to.nested.A == from.nested.A { + t.Errorf("should be different") + } + }) + + t.Run("Should work with exported ptr fields", func(t *testing.T) { + type Nested struct { + A string + } + type parentA struct { + *Nested + } + type parentB struct { + *Nested + } + + fieldValue := "a" + from := parentA{Nested: &Nested{A: fieldValue}} + to := parentB{} + + err := copier.CopyWithOption(&to, &from, copier.Option{ + DeepCopy: true, + }) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + from.Nested.A = "b" + + if to.Nested.A != fieldValue { + t.Errorf("should not change") + } + }) + + t.Run("Should work with exported ptr fields with same name src field", func(t *testing.T) { + type Nested struct { + A string + } + type parentA struct { + A string + } + type parentB struct { + *Nested + } + + fieldValue := "a" + from := parentA{A: fieldValue} + to := parentB{} + + err := copier.CopyWithOption(&to, &from, copier.Option{ + DeepCopy: true, + }) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + from.A = "b" + + if to.Nested.A != fieldValue { + t.Errorf("should not change") + } + }) + + t.Run("Should work with exported fields", func(t *testing.T) { + type Nested struct { + A string + } + type parentA struct { + Nested + } + type parentB struct { + Nested + } + + fieldValue := "a" + from := parentA{Nested: Nested{A: fieldValue}} + to := parentB{} + + err := copier.CopyWithOption(&to, &from, copier.Option{ + DeepCopy: true, + }) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + from.Nested.A = "b" + + if to.Nested.A != fieldValue { + t.Errorf("should not change") + } + }) +} + +type someStructInsensitive struct { + IntField int + UIntField uint64 +} + +type structSameName1Insensitive struct { + A string + B int64 + C time.Time + D string + E *someStructInsensitive +} + +type structSameName2Insensitive struct { + A string + B time.Time + C int64 + D string + E *someStructInsensitive +} + +func TestCopyFieldsWithSameNameButDifferentTypesInsensitive(t *testing.T) { + var optionsInsensitive = copier.Option{CaseInsensitive: true} + obj1 := structSameName1Insensitive{A: "123", B: 2, C: time.Now()} + obj2 := &structSameName2Insensitive{} + err := copier.CopyWithOption(obj2, &obj1, optionsInsensitive) + if err != nil { + t.Error("Should not raise error") + } + + if obj2.A != obj1.A { + t.Errorf("Field A should be copied") + } +} + +type Foo1Insensitive struct { + Name string + Age int32 +} + +type Foo2Insensitive struct { + Name string +} + +type StructWithMap1Insensitive struct { + Map map[int]Foo1Insensitive +} + +type StructWithMap2Insensitive struct { + Map map[int32]Foo2Insensitive +} + +func TestCopyMapOfStructInsensitive(t *testing.T) { + var optionsInsensitive = copier.Option{CaseInsensitive: true} + obj1 := StructWithMap1Insensitive{Map: map[int]Foo1Insensitive{2: {Name: "A pure foo"}}} + obj2 := &StructWithMap2Insensitive{} + err := copier.CopyWithOption(obj2, obj1, optionsInsensitive) + if err != nil { + t.Error("Should not raise error") + } + for k, v1 := range obj1.Map { + v2, ok := obj2.Map[int32(k)] + if !ok || v1.Name != v2.Name { + t.Errorf("Map should be copied") + } + } +} + +func TestCopyMapOfIntInsensitive(t *testing.T) { + var optionsInsensitive = copier.Option{CaseInsensitive: true} + map1 := map[int]int{3: 6, 4: 8} + map2 := map[int32]int8{} + err := copier.CopyWithOption(&map2, map1, optionsInsensitive) + if err != nil { + t.Error("Should not raise error") + } + + for k, v1 := range map1 { + v2, ok := map2[int32(k)] + if !ok || v1 != int(v2) { + t.Errorf("Map should be copied") + } + } +} + +func TestCopyMapOfSliceValueInsensitive(t *testing.T) { + var optionsInsensitive = copier.Option{CaseInsensitive: true} + // case1: map's value is a simple slice + key, value := 2, 3 + src := map[int][]int{key: {value}} + + dst1 := map[int][]int{} + var dst2 map[int][]int + err := copier.CopyWithOption(&dst1, src, optionsInsensitive) + if err != nil { + t.Error("Should not raise error") + } + err = copier.CopyWithOption(&dst2, src, optionsInsensitive) + if err != nil { + t.Error("Should not raise error") + } + + for k, v1 := range src { + v2, ok := dst1[k] + if !ok || len(v1) != len(v2) || k != key { + t.Errorf("Map should be copied") + } + for i := range v1 { + if v2[i] != value { + t.Errorf("Map's slice value shoud be copied") + } + } + + v3, ok := dst2[k] + if !ok || len(v1) != len(v3) { + t.Errorf("Map should be copied") + } + for i := range v1 { + if v3[i] != value { + t.Errorf("Map's slice value shoud be copied") + } + } + } + + // case2: map's value is a slice whose element is map + key1, key2 := 2, 3 + value = 4 + s := map[int][]map[int]int{key1: {{key2: value}}} + d1 := map[int][]map[int]int{key1: {{key1: key2}}} + d2 := map[int][]map[int]int{key1: {}} + d3 := map[int][]map[int]int{key1: nil} + d4 := map[int][]map[int]int{} + d5 := map[int][]map[int]int(nil) + ms := []map[int][]map[int]int{d1, d2, d3, d4, d5} + for i := range ms { + copier.CopyWithOption(&ms[i], s, copier.Option{IgnoreEmpty: false, DeepCopy: true}) + + if len(ms[i]) != len(s) { + t.Errorf("Number of map's keys should be equal") + } + for k, sliceMap := range ms[i] { + if k != key1 { + t.Errorf("Map's key should be copied") + } + if len(sliceMap) != len(s[key1]) || len(sliceMap) != 1 { + t.Errorf("Map's slice value should be copied") + } + m := sliceMap[0] + if len(m) != len(s[key1][0]) || len(m) != 1 { + t.Errorf("Map's slice value should be copied recursively") + } + for k, v := range m { + if k != key2 || v != value { + t.Errorf("Map's slice value should be copied recursively") + } + } + } + } +} + +func TestCopyMapOfPtrValueInsensitive(t *testing.T) { + var optionsInsensitive = copier.Option{CaseInsensitive: true} + intV := 3 + intv := intV + src := map[int]*int{2: &intv} + dst1 := map[int]*int{} + var dst2 map[int]*int + err := copier.CopyWithOption(&dst1, src, optionsInsensitive) + if err != nil { + t.Error("Should not raise error") + } + err = copier.CopyWithOption(&dst2, src, optionsInsensitive) + if err != nil { + t.Error("Should not raise error") + } + + for k, v1 := range src { + v2, ok := dst1[k] + if !ok || v2 == nil || v1 == nil || *v2 != *v1 || *v2 != intV { + t.Errorf("Map should be copied") + } + + v3, ok := dst2[k] + if !ok || v3 == nil || *v3 != *v1 || *v3 != intV { + t.Errorf("Map should be copied") + } + } +} + +func TestCopyWithOptionInsensitive(t *testing.T) { + from := structSameName2Insensitive{D: "456", E: &someStructInsensitive{IntField: 100, UIntField: 1000}} + to := &structSameName1Insensitive{A: "123", B: 2, C: time.Now(), D: "123", E: &someStructInsensitive{UIntField: 5000}} + if err := copier.CopyWithOption(to, &from, copier.Option{IgnoreEmpty: true}); err != nil { + t.Error("Should not raise error") + } + + if to.A == from.A { + t.Errorf("Field A should not be copied") + } else if to.D != from.D { + t.Errorf("Field D should be copied") + } +} + +type ScannerValueInsensitive struct { + V int +} + +func (s *ScannerValueInsensitive) Scan(src interface{}) error { + return errors.New("I failed") +} + +type ScannerStructInsensitive struct { + V *ScannerValueInsensitive +} + +type ScannerStructToInsensitive struct { + V *ScannerValueInsensitive +} + +func TestScannerInsensitive(t *testing.T) { + var optionsInsensitive = copier.Option{CaseInsensitive: true} + s := &ScannerStructInsensitive{ + V: &ScannerValueInsensitive{ + V: 12, + }, + } + + s2 := &ScannerStructToInsensitive{} + + err := copier.CopyWithOption(s2, s, optionsInsensitive) + if err != nil { + t.Error("Should not raise error") + } + + if s.V.V != s2.V.V { + t.Errorf("Field V should be copied") + } +} + +func TestScanFromPtrToSqlNullableInsensitive(t *testing.T) { + var optionsInsensitive = copier.Option{CaseInsensitive: true} + var ( + from struct { + S string + Sptr *string + Snull sql.NullString + T1 sql.NullTime + T2 sql.NullTime + T3 *time.Time + } + + to struct { + S sql.NullString + Sptr sql.NullString + Snull *string + T1 time.Time + T2 *time.Time + T3 sql.NullTime + } + + s string + + err error + ) + + s = "test" + from.S = s + from.Sptr = &s + + if from.T1.Valid || from.T2.Valid { + t.Errorf("Must be not valid") + } + + err = copier.CopyWithOption(&to, from, optionsInsensitive) + if err != nil { + t.Error("Should not raise error") + } + + if !to.T1.IsZero() { + t.Errorf("to.T1 should be Zero but %v", to.T1) + } + + if to.T2 != nil { + t.Errorf("to.T2 should be nil but %v", to.T2) + } + + if to.Snull != nil { + t.Errorf("to.Snull should be nil but %v", to.Snull) + } + + now := time.Now() + + from.T1.Scan(now) + from.T2.Scan(now) + + err = copier.CopyWithOption(&to, from, optionsInsensitive) + if err != nil { + t.Error("Should not raise error") + } + + if to.S.String != from.S { + t.Errorf("Field S should be copied") + } + + if to.Sptr.String != *from.Sptr { + t.Errorf("Field Sptr should be copied") + } + + if from.T1.Time != to.T1 { + t.Errorf("Fields T1 fields should be equal") + } + + if from.T2.Time != *to.T2 { + t.Errorf("Fields T2 fields should be equal") + } +} + +func TestDeepCopyInterfaceInsensitive(t *testing.T) { + m := make(map[string]string) + m["a"] = "ccc" + + from := []interface{}{[]int{7, 8, 9}, 2, 3, m, errors.New("aaaa")} + var to []interface{} + + copier.CopyWithOption(&to, &from, copier.Option{ + IgnoreEmpty: false, + DeepCopy: true, + }) + + from[0].([]int)[0] = 10 + from[1] = "3" + from[3].(map[string]string)["a"] = "bbb" + + if fmt.Sprint(to[0]) != fmt.Sprint([]int{7, 8, 9}) { + t.Errorf("to value failed to be deep copied") + } + + if fmt.Sprint(to[1]) != "2" { + t.Errorf("to value failed to be deep copied") + } + + if to[3].(map[string]string)["a"] != "ccc" { + t.Errorf("to value failed to be deep copied") + } +} + +func TestDeepCopyTimeInsensitive(t *testing.T) { + type embedT1 struct { + T5 time.Time + } + + type embedT2 struct { + T6 *time.Time + } + + var ( + from struct { + T1 time.Time + T2 *time.Time + + T3 *time.Time + T4 time.Time + T5 time.Time + T6 time.Time + } + + to struct { + T1 time.Time + T2 *time.Time + + T3 time.Time + T4 *time.Time + embedT1 + embedT2 + } + ) + + t1 := time.Now() + from.T1 = t1 + t2 := t1.Add(time.Second) + from.T2 = &t2 + t3 := t2.Add(time.Second) + from.T3 = &t3 + t4 := t3.Add(time.Second) + from.T4 = t4 + t5 := t4.Add(time.Second) + from.T5 = t5 + t6 := t5.Add(time.Second) + from.T6 = t6 + + err := copier.CopyWithOption(&to, from, copier.Option{DeepCopy: true}) + if err != nil { + t.Error("Should not raise error") + } + + if !to.T1.Equal(from.T1) { + t.Errorf("Field T1 should be copied") + } + if !to.T2.Equal(*from.T2) { + t.Errorf("Field T2 should be copied") + } + if !to.T3.Equal(*from.T3) { + t.Errorf("Field T3 should be copied") + } + if !to.T4.Equal(from.T4) { + t.Errorf("Field T4 should be copied") + } + if !to.T5.Equal(from.T5) { + t.Errorf("Field T5 should be copied") + } + if !to.T6.Equal(from.T6) { + t.Errorf("Field T6 should be copied") + } +} + +func TestNestedPrivateDataInsensitive(t *testing.T) { + var optionsInsensitive = copier.Option{CaseInsensitive: true} + type hasPrivate struct { + data int + } + + type hasMembers struct { + Member hasPrivate + } + + src := hasMembers{ + Member: hasPrivate{ + data: 42, + }, + } + var shallow hasMembers + err := copier.CopyWithOption(&shallow, &src, optionsInsensitive) + if err != nil { + t.Errorf("could not complete shallow copy") + } + if !reflect.DeepEqual(&src, &shallow) { + t.Errorf("shallow copy faild") + } + + var deep hasMembers + err = copier.CopyWithOption(&deep, &src, copier.Option{DeepCopy: true}) + if err != nil { + t.Errorf("could not complete deep copy") + } + if !reflect.DeepEqual(&src, &deep) { + t.Errorf("deep copy faild") + } + + if !reflect.DeepEqual(&shallow, &deep) { + t.Errorf("unexpected difference between shallow and deep copy") + } +} + +func TestDeepMapCopyTimeInsensitive(t *testing.T) { + t1 := time.Now() + t2 := t1.Add(time.Second) + from := []map[string]interface{}{ + { + "t1": t1, + "t2": &t2, + }, + } + to := make([]map[string]interface{}, len(from)) + + err := copier.CopyWithOption(&to, from, copier.Option{DeepCopy: true}) + if err != nil { + t.Error("should not error") + } + if len(to) != len(from) { + t.Errorf("slice should be copied") + } + if !to[0]["t1"].(time.Time).Equal(from[0]["t1"].(time.Time)) { + t.Errorf("nested time ptr should be copied") + } + if !to[0]["t2"].(*time.Time).Equal(*from[0]["t2"].(*time.Time)) { + t.Errorf("nested time ptr should be copied") + } +} + +func TestCopySimpleTimeInsensitive(t *testing.T) { + var optionsInsensitive = copier.Option{CaseInsensitive: true} + from := time.Now() + to := time.Time{} + + err := copier.CopyWithOption(&to, from, optionsInsensitive) + if err != nil { + t.Error("should not error") + } + if !from.Equal(to) { + t.Errorf("to (%v) value should equal from (%v) value", to, from) + } +} + +func TestDeepCopySimpleTimeInsensitive(t *testing.T) { + from := time.Now() + to := time.Time{} + + err := copier.CopyWithOption(&to, from, copier.Option{DeepCopy: true}) + if err != nil { + t.Error("should not error") + } + if !from.Equal(to) { + t.Errorf("to (%v) value should equal from (%v) value", to, from) + } +} + +type TimeWrapperInsensitive struct { + time.Time +} + +func TestDeepCopyAnonymousFieldTimeInsensitive(t *testing.T) { + from := TimeWrapperInsensitive{time.Now()} + to := TimeWrapperInsensitive{} + + err := copier.CopyWithOption(&to, from, copier.Option{DeepCopy: true}) + if err != nil { + t.Error("should not error") + } + if !from.Time.Equal(to.Time) { + t.Errorf("to (%v) value should equal from (%v) value", to.Time, from.Time) + } +} + +func TestSqlNullFiledInsensitive(t *testing.T) { + var optionsInsensitive = copier.Option{CaseInsensitive: true} + + type sqlStruct struct { + MkId sql.NullInt64 + MkExpiryDateType sql.NullInt32 + MkExpiryDateStart sql.NullString + } + + type dataStruct struct { + MkId int64 + MkExpiryDateType int32 + MkExpiryDateStart string + } + + from := sqlStruct{ + MkId: sql.NullInt64{Int64: 3, Valid: true}, + MkExpiryDateType: sql.NullInt32{Int32: 4, Valid: true}, + MkExpiryDateStart: sql.NullString{String: "5", Valid: true}, + } + + to := dataStruct{} + + err := copier.CopyWithOption(&to, from, optionsInsensitive) + if err != nil { + t.Error("should not error") + } + if from.MkId.Int64 != to.MkId { + t.Errorf("to (%v) value should equal from (%v) value", to.MkId, from.MkId.Int64) + } + + if from.MkExpiryDateStart.String != to.MkExpiryDateStart { + t.Errorf("to (%v) value should equal from (%v) value", to.MkExpiryDateStart, from.MkExpiryDateStart.String) + } + + if from.MkExpiryDateType.Int32 != to.MkExpiryDateType { + t.Errorf("to (%v) value should equal from (%v) value", to.MkExpiryDateType, from.MkExpiryDateType.Int32) + } +} + +func TestEmptySliceInsensitive(t *testing.T) { + var optionsInsensitive = copier.Option{CaseInsensitive: true} + type Str1 string + type Str2 string + type Input1 struct { + Val Str1 + } + type Input2 struct { + Val Str2 + } + to := []*Input1(nil) + from := []*Input2{} + err := copier.CopyWithOption(&to, &from, optionsInsensitive) + if err != nil { + t.Error("should not error") + } + if from == nil { + t.Error("from should be empty slice not nil") + } + + to = []*Input1(nil) + from = []*Input2(nil) + err = copier.CopyWithOption(&to, &from, optionsInsensitive) + if err != nil { + t.Error("should not error") + } + if from != nil { + t.Error("from should be empty slice nil") + } +} + +func TestNestedNilPointerStructInsensitive(t *testing.T) { + var optionsInsensitive = copier.Option{CaseInsensitive: true} + type destination struct { + Title string + } + + type NestedSource struct { + ID int + } + + type source struct { + Title string + *NestedSource + } + + from := &source{ + Title: "A title to be copied", + } + + to := destination{} + + err := copier.CopyWithOption(&to, from, optionsInsensitive) + if err != nil { + t.Error("should not error") + } + + if from.Title != to.Title { + t.Errorf("to (%v) value should equal from (%v) value", to.Title, from.Title) + } +} diff --git a/copier_test.go b/copier_test.go index 4769eba..c31e997 100644 --- a/copier_test.go +++ b/copier_test.go @@ -30,7 +30,7 @@ type Employee struct { _User *User Name string Birthday *time.Time - NickName *string + Nickname *string Age int64 FakeAge int EmployeID int64 @@ -45,10 +45,11 @@ func (employee *Employee) Role(role string) { } func checkEmployee(employee Employee, user User, t *testing.T, testCase string) { + t.Helper() if employee.Name != user.Name { t.Errorf("%v: Name haven't been copied correctly.", testCase) } - if employee.NickName == nil || *employee.NickName != user.Nickname { + if employee.Nickname == nil || *employee.Nickname != user.Nickname { t.Errorf("%v: NickName haven't been copied correctly.", testCase) } if employee.Birthday == nil && user.Birthday != nil { @@ -101,8 +102,9 @@ func TestCopySameStructWithPointerField(t *testing.T) { } func checkEmployee2(employee Employee, user *User, t *testing.T, testCase string) { + t.Helper() if user == nil { - if employee.Name != "" || employee.NickName != nil || employee.Birthday != nil || employee.Age != 0 || + if employee.Name != "" || employee.Nickname != nil || employee.Birthday != nil || employee.Age != 0 || employee.DoubleAge != 0 || employee.FakeAge != 0 || employee.SuperRule != "" || employee.Notes != nil { t.Errorf("%v : employee should be empty", testCase) }