├── .gitignore ├── go.mod ├── errors.go ├── .github └── workflows │ └── tests.yml ├── copier_field_name_mapping_test.go ├── License ├── copier_benchmark_test.go ├── copier_issue84_test.go ├── copier_issue170_test.go ├── copier_different_type_test.go ├── copier_tags_test.go ├── copier_converter_test.go ├── README.md ├── copier.go └── copier_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | ttt/ 3 | cmd/ 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jinzhu/copier 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package copier 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrInvalidCopyDestination = errors.New("copy destination must be non-nil and addressable") 7 | ErrInvalidCopyFrom = errors.New("copy from must be non-nil and addressable") 8 | ErrMapKeyNotMatch = errors.New("map's key type doesn't match") 9 | ErrNotSupported = errors.New("not supported") 10 | ErrFieldNameTagStartNotUpperCase = errors.New("copier field name tag must be start upper case") 11 | ) 12 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'gh-pages' 7 | pull_request: 8 | branches-ignore: 9 | - 'gh-pages' 10 | 11 | jobs: 12 | ci: 13 | strategy: 14 | matrix: 15 | go: ['1.17', '1.18', '1.19', '1.20', '1.21', '1.22'] 16 | platform: [ubuntu-latest, macos-latest] # can not run in windows OS 17 | runs-on: ${{ matrix.platform }} 18 | 19 | steps: 20 | - name: Set up Go 1.x 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version: ${{ matrix.go }} 24 | 25 | - name: Check out code into the Go module directory 26 | uses: actions/checkout@v4 27 | 28 | - name: Tests 29 | run: go test 30 | -------------------------------------------------------------------------------- /copier_field_name_mapping_test.go: -------------------------------------------------------------------------------- 1 | package copier_test 2 | 3 | import ( 4 | "github.com/jinzhu/copier" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestCustomFieldName(t *testing.T) { 10 | type User1 struct { 11 | Id int64 12 | Name string 13 | Address []string 14 | } 15 | 16 | type User2 struct { 17 | Id2 int64 18 | Name2 string 19 | Address2 []string 20 | } 21 | 22 | u1 := User1{Id: 1, Name: "1", Address: []string{"1"}} 23 | var u2 User2 24 | err := copier.CopyWithOption(&u2, u1, copier.Option{FieldNameMapping: []copier.FieldNameMapping{ 25 | {SrcType: u1, DstType: u2, 26 | Mapping: map[string]string{ 27 | "Id": "Id2", 28 | "Name": "Name2", 29 | "Address": "Address2"}}, 30 | }}) 31 | 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | 36 | if u1.Id != u2.Id2 { 37 | t.Error("copy id failed.") 38 | } 39 | 40 | if u1.Name != u2.Name2 { 41 | t.Error("copy name failed.") 42 | } 43 | 44 | if !reflect.DeepEqual(u1.Address, u2.Address2) { 45 | t.Error("copy address failed.") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jinzhu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /copier_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package copier_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/jinzhu/copier" 8 | ) 9 | 10 | func BenchmarkCopyStruct(b *testing.B) { 11 | var fakeAge int32 = 12 12 | user := User{Name: "Jinzhu", Nickname: "jinzhu", Age: 18, FakeAge: &fakeAge, Role: "Admin", Notes: []string{"hello world", "welcome"}, flags: []byte{'x'}} 13 | for x := 0; x < b.N; x++ { 14 | copier.Copy(&Employee{}, &user) 15 | } 16 | } 17 | 18 | func BenchmarkNamaCopy(b *testing.B) { 19 | var fakeAge int32 = 12 20 | user := User{Name: "Jinzhu", Nickname: "jinzhu", Age: 18, FakeAge: &fakeAge, Role: "Admin", Notes: []string{"hello world", "welcome"}, flags: []byte{'x'}} 21 | for x := 0; x < b.N; x++ { 22 | employee := &Employee{ 23 | Name: user.Name, 24 | NickName: &user.Nickname, 25 | Age: int64(user.Age), 26 | FakeAge: int(*user.FakeAge), 27 | DoubleAge: user.DoubleAge(), 28 | } 29 | 30 | for _, note := range user.Notes { 31 | employee.Notes = append(employee.Notes, ¬e) 32 | } 33 | employee.Role(user.Role) 34 | } 35 | } 36 | 37 | func BenchmarkJsonMarshalCopy(b *testing.B) { 38 | var fakeAge int32 = 12 39 | user := User{Name: "Jinzhu", Nickname: "jinzhu", Age: 18, FakeAge: &fakeAge, Role: "Admin", Notes: []string{"hello world", "welcome"}, flags: []byte{'x'}} 40 | for x := 0; x < b.N; x++ { 41 | data, _ := json.Marshal(user) 42 | var employee Employee 43 | json.Unmarshal(data, &employee) 44 | 45 | employee.DoubleAge = user.DoubleAge() 46 | employee.Role(user.Role) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /copier_issue84_test.go: -------------------------------------------------------------------------------- 1 | package copier_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/jinzhu/copier" 8 | ) 9 | 10 | type Embedded struct { 11 | Field1 string 12 | Field2 string 13 | } 14 | 15 | type Embedder struct { 16 | Embedded 17 | PtrField *string 18 | } 19 | 20 | type Timestamps struct { 21 | CreatedAt time.Time `json:"created_at"` 22 | UpdatedAt time.Time `json:"updated_at"` 23 | } 24 | 25 | type NotWork struct { 26 | ID string `json:"id"` 27 | UserID *string `json:"user_id"` 28 | Name string `json:"name"` 29 | Website *string `json:"website"` 30 | Timestamps 31 | } 32 | 33 | type Work struct { 34 | ID string `json:"id"` 35 | Name string `json:"name"` 36 | UserID *string `json:"user_id"` 37 | Website *string `json:"website"` 38 | Timestamps 39 | } 40 | 41 | func TestIssue84(t *testing.T) { 42 | t.Run("test1", func(t *testing.T) { 43 | var embedder Embedder 44 | embedded := Embedded{ 45 | Field1: "1", 46 | Field2: "2", 47 | } 48 | err := copier.Copy(&embedder, &embedded) 49 | if err != nil { 50 | t.Errorf("unable to copy: %s", err) 51 | } 52 | if embedder.Field1 != embedded.Field1 { 53 | t.Errorf("field1 value is %s instead of %s", embedder.Field1, embedded.Field1) 54 | } 55 | if embedder.Field2 != embedded.Field2 { 56 | t.Errorf("field2 value is %s instead of %s", embedder.Field2, embedded.Field2) 57 | } 58 | }) 59 | t.Run("from issue", func(t *testing.T) { 60 | notWorkObj := NotWork{ 61 | ID: "123", 62 | Name: "name", 63 | Website: nil, 64 | UserID: nil, 65 | Timestamps: Timestamps{ 66 | UpdatedAt: time.Now(), 67 | }, 68 | } 69 | workObj := Work{ 70 | ID: "123", 71 | Name: "name", 72 | Website: nil, 73 | UserID: nil, 74 | Timestamps: Timestamps{ 75 | UpdatedAt: time.Now(), 76 | }, 77 | } 78 | 79 | destObj1 := Work{} 80 | destObj2 := NotWork{} 81 | 82 | copier.CopyWithOption(&destObj1, &workObj, copier.Option{IgnoreEmpty: true, DeepCopy: false}) 83 | 84 | copier.CopyWithOption(&destObj2, ¬WorkObj, copier.Option{IgnoreEmpty: true, DeepCopy: false}) 85 | }) 86 | } 87 | -------------------------------------------------------------------------------- /copier_issue170_test.go: -------------------------------------------------------------------------------- 1 | package copier_test 2 | 3 | import ( 4 | "github.com/jinzhu/copier" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | type A struct { 10 | A int 11 | } 12 | type B struct { 13 | A int 14 | b int 15 | } 16 | 17 | var copied = B{A: 2387483274, b: 128387134} 18 | 19 | func newOptWithConverter() copier.Option { 20 | return copier.Option{ 21 | Converters: []copier.TypeConverter{ 22 | { 23 | SrcType: A{}, 24 | DstType: B{}, 25 | Fn: func(from interface{}) (interface{}, error) { 26 | return copied, nil 27 | }, 28 | }, 29 | }, 30 | } 31 | } 32 | 33 | func Test_Struct_With_Converter(t *testing.T) { 34 | aa := A{A: 11} 35 | bb := B{A: 10, b: 100} 36 | err := copier.CopyWithOption(&bb, &aa, newOptWithConverter()) 37 | if err != nil || !reflect.DeepEqual(copied, bb) { 38 | t.Fatalf("Got %v, wanted %v", bb, copied) 39 | } 40 | } 41 | 42 | func Test_Map_With_Converter(t *testing.T) { 43 | aa := map[string]*A{ 44 | "a": &A{A: 10}, 45 | } 46 | 47 | bb := map[string]*B{ 48 | "a": &B{A: 10, b: 100}, 49 | } 50 | 51 | err := copier.CopyWithOption(&bb, &aa, newOptWithConverter()) 52 | if err != nil { 53 | t.Fatalf("copy with converter failed: %v", err) 54 | } 55 | 56 | for _, v := range bb { 57 | wanted := &copied 58 | if !reflect.DeepEqual(v, wanted) { 59 | t.Fatalf("Got %v, wanted %v", v, wanted) 60 | } 61 | } 62 | } 63 | 64 | func Test_Slice_With_Converter(t *testing.T) { 65 | aa := []*A{ 66 | &A{A: 10}, 67 | } 68 | 69 | bb := []*B{ 70 | &B{A: 10, b: 100}, 71 | } 72 | 73 | err := copier.CopyWithOption(&bb, &aa, newOptWithConverter()) 74 | 75 | if err != nil { 76 | t.Fatalf("copy slice error: %v", err) 77 | } 78 | 79 | wanted := copied 80 | for _, v := range bb { 81 | temp := v 82 | if !reflect.DeepEqual(*temp, wanted) { 83 | t.Fatalf("Got %v, wanted %v", *temp, wanted) 84 | } 85 | } 86 | } 87 | 88 | func Test_Slice_Embedded_With_Converter(t *testing.T) { 89 | aa := struct { 90 | A []*A 91 | }{ 92 | A: []*A{&A{A: 10}}, 93 | } 94 | 95 | bb := struct { 96 | A []*B 97 | }{ 98 | A: []*B{&B{A: 10, b: 100}}, 99 | } 100 | 101 | err := copier.CopyWithOption(&bb, &aa, newOptWithConverter()) 102 | 103 | wanted := struct { 104 | A []*B 105 | }{ 106 | A: []*B{&copied}, 107 | } 108 | 109 | if err != nil || !reflect.DeepEqual(bb, wanted) { 110 | t.Fatalf("Got %v, wanted %v", bb, wanted) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /copier_different_type_test.go: -------------------------------------------------------------------------------- 1 | package copier_test 2 | 3 | import ( 4 | "database/sql" 5 | "testing" 6 | "time" 7 | 8 | "github.com/jinzhu/copier" 9 | ) 10 | 11 | type TypeStruct1 struct { 12 | Field1 string 13 | Field2 string 14 | Field3 TypeStruct2 15 | Field4 *TypeStruct2 16 | Field5 []*TypeStruct2 17 | Field6 []TypeStruct2 18 | Field7 []*TypeStruct2 19 | Field8 []TypeStruct2 20 | Field9 []string 21 | } 22 | 23 | type TypeStruct2 struct { 24 | Field1 int 25 | Field2 string 26 | Field3 []TypeStruct2 27 | Field4 *TypeStruct2 28 | Field5 *TypeStruct2 29 | Field9 string 30 | } 31 | 32 | type TypeStruct3 struct { 33 | Field1 interface{} 34 | Field2 string 35 | Field3 TypeStruct4 36 | Field4 *TypeStruct4 37 | Field5 []*TypeStruct4 38 | Field6 []*TypeStruct4 39 | Field7 []TypeStruct4 40 | Field8 []TypeStruct4 41 | } 42 | 43 | type TypeStruct4 struct { 44 | field1 int 45 | Field2 string 46 | } 47 | 48 | func (t *TypeStruct4) Field1(i int) { 49 | t.field1 = i 50 | } 51 | 52 | type TypeBaseStruct5 struct { 53 | A bool 54 | B byte 55 | C float64 56 | D int16 57 | E int32 58 | F int64 59 | G time.Time 60 | H string 61 | } 62 | 63 | type TypeSqlNullStruct6 struct { 64 | A sql.NullBool `json:"a"` 65 | B sql.NullByte `json:"b"` 66 | C sql.NullFloat64 `json:"c"` 67 | D sql.NullInt16 `json:"d"` 68 | E sql.NullInt32 `json:"e"` 69 | F sql.NullInt64 `json:"f"` 70 | G sql.NullTime `json:"g"` 71 | H sql.NullString `json:"h"` 72 | } 73 | 74 | func TestCopyDifferentFieldType(t *testing.T) { 75 | ts := &TypeStruct1{ 76 | Field1: "str1", 77 | Field2: "str2", 78 | } 79 | ts2 := &TypeStruct2{} 80 | 81 | copier.Copy(ts2, ts) 82 | 83 | if ts2.Field2 != ts.Field2 || ts2.Field1 != 0 { 84 | t.Errorf("Should be able to copy from ts to ts2") 85 | } 86 | } 87 | 88 | func TestCopyDifferentTypeMethod(t *testing.T) { 89 | ts := &TypeStruct1{ 90 | Field1: "str1", 91 | Field2: "str2", 92 | } 93 | ts4 := &TypeStruct4{} 94 | 95 | copier.Copy(ts4, ts) 96 | 97 | if ts4.Field2 != ts.Field2 || ts4.field1 != 0 { 98 | t.Errorf("Should be able to copy from ts to ts4") 99 | } 100 | } 101 | 102 | func TestAssignableType(t *testing.T) { 103 | ts := &TypeStruct1{ 104 | Field1: "str1", 105 | Field2: "str2", 106 | Field3: TypeStruct2{ 107 | Field1: 666, 108 | Field2: "str2", 109 | }, 110 | Field4: &TypeStruct2{ 111 | Field1: 666, 112 | Field2: "str2", 113 | }, 114 | Field5: []*TypeStruct2{ 115 | { 116 | Field1: 666, 117 | Field2: "str2", 118 | }, 119 | }, 120 | Field6: []TypeStruct2{ 121 | { 122 | Field1: 666, 123 | Field2: "str2", 124 | }, 125 | }, 126 | Field7: []*TypeStruct2{ 127 | { 128 | Field1: 666, 129 | Field2: "str2", 130 | }, 131 | }, 132 | } 133 | 134 | ts3 := &TypeStruct3{} 135 | 136 | copier.CopyWithOption(&ts3, &ts, copier.Option{CaseSensitive: true}) 137 | 138 | if v, ok := ts3.Field1.(string); !ok { 139 | t.Error("Assign to interface{} type did not succeed") 140 | } else if v != "str1" { 141 | t.Error("String haven't been copied correctly") 142 | } 143 | 144 | if ts3.Field2 != ts.Field2 { 145 | t.Errorf("Field2 should be copied") 146 | } 147 | 148 | checkType2WithType4(ts.Field3, ts3.Field3, t, "Field3") 149 | checkType2WithType4(*ts.Field4, *ts3.Field4, t, "Field4") 150 | 151 | if len(ts3.Field5) != len(ts.Field5) { 152 | t.Fatalf("fields not equal, got %v, expects: %v", len(ts3.Field5), len(ts.Field5)) 153 | } 154 | 155 | for idx, f := range ts.Field5 { 156 | checkType2WithType4(*f, *(ts3.Field5[idx]), t, "Field5") 157 | } 158 | 159 | for idx, f := range ts.Field6 { 160 | checkType2WithType4(f, *(ts3.Field6[idx]), t, "Field6") 161 | } 162 | 163 | for idx, f := range ts.Field7 { 164 | checkType2WithType4(*f, ts3.Field7[idx], t, "Field7") 165 | } 166 | 167 | for idx, f := range ts.Field8 { 168 | checkType2WithType4(f, ts3.Field8[idx], t, "Field8") 169 | } 170 | } 171 | 172 | func checkType2WithType4(t2 TypeStruct2, t4 TypeStruct4, t *testing.T, testCase string) { 173 | if t2.Field1 != t4.field1 || t2.Field2 != t4.Field2 { 174 | t.Errorf("%v: type struct 4 and type struct 2 is not equal", testCase) 175 | } 176 | } 177 | 178 | func TestCopyFromBaseToSqlNullWithOptionDeepCopy(t *testing.T) { 179 | a := TypeBaseStruct5{ 180 | A: true, 181 | B: byte(2), 182 | C: 5.5, 183 | D: 1, 184 | E: 2, 185 | F: 3, 186 | G: time.Now(), 187 | H: "deep", 188 | } 189 | b := TypeSqlNullStruct6{} 190 | 191 | err := copier.CopyWithOption(&b, a, copier.Option{DeepCopy: true}) 192 | // 检查是否有错误 193 | if err != nil { 194 | t.Errorf("CopyStructWithOption() error = %v", err) 195 | return 196 | } 197 | // 检查 b 结构体的字段是否符合预期 198 | if !b.A.Valid || b.A.Bool != true { 199 | t.Errorf("b.A = %v, want %v", b.A, true) 200 | } 201 | if !b.B.Valid || b.B.Byte != byte(2) { 202 | t.Errorf("b.B = %v, want %v", b.B, byte(2)) 203 | } 204 | if !b.C.Valid || b.C.Float64 != 5.5 { 205 | t.Errorf("b.C = %v, want %v", b.C, 5.5) 206 | } 207 | if !b.D.Valid || b.D.Int16 != 1 { 208 | t.Errorf("b.D = %v, want %v", b.D, 1) 209 | } 210 | if !b.E.Valid || b.E.Int32 != 2 { 211 | t.Errorf("b.E = %v, want %v", b.E, 2) 212 | } 213 | if !b.F.Valid || b.F.Int64 != 3 { 214 | t.Errorf("b.F = %v, want %v", b.F, 3) 215 | } 216 | if !b.G.Valid || b.G.Time != a.G { 217 | t.Errorf("b.G = %v, want %v", b.G, a.G) 218 | } 219 | if !b.H.Valid || b.H.String != "deep" { 220 | t.Errorf("b.H = %v, want %v", b.H, "deep") 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /copier_tags_test.go: -------------------------------------------------------------------------------- 1 | package copier_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jinzhu/copier" 7 | ) 8 | 9 | type EmployeeTags struct { 10 | Name string `copier:"must"` 11 | DOB string 12 | Address string 13 | ID int `copier:"-"` 14 | } 15 | 16 | type EmployeeTags2 struct { 17 | Name string `copier:"must,nopanic"` 18 | DOB string 19 | Address string 20 | ID int `copier:"-"` 21 | } 22 | 23 | type EmployeeTags3 struct { 24 | Name string 25 | DOB string 26 | Address string 27 | ID int 28 | } 29 | 30 | type User1 struct { 31 | Name string 32 | DOB string 33 | Address string `copier:"override"` 34 | ID int 35 | } 36 | 37 | type User2 struct { 38 | DOB string 39 | Address *string `copier:"override"` 40 | ID int 41 | } 42 | 43 | func TestCopyTagIgnore(t *testing.T) { 44 | employee := EmployeeTags{ID: 100} 45 | user := User1{Name: "Dexter Ledesma", DOB: "1 November, 1970", Address: "21 Jump Street", ID: 12345} 46 | copier.Copy(&employee, user) 47 | if employee.ID == user.ID { 48 | t.Error("Was not expected to copy IDs") 49 | } 50 | if employee.ID != 100 { 51 | t.Error("Original ID was overwritten") 52 | } 53 | } 54 | 55 | func TestCopyTagMust(t *testing.T) { 56 | employee := &EmployeeTags{} 57 | user := &User2{DOB: "1 January 1970"} 58 | defer func() { 59 | if r := recover(); r == nil { 60 | t.Error("Expected a panic.") 61 | } 62 | }() 63 | copier.Copy(employee, user) 64 | } 65 | 66 | func TestCopyTagMustByOption(t *testing.T) { 67 | employee := &EmployeeTags3{} 68 | user := &User2{DOB: "1 January 1970"} 69 | t.Run("must is true", func(t *testing.T) { 70 | defer func() { 71 | if r := recover(); r == nil { 72 | t.Error("Expected a panic.") 73 | } 74 | }() 75 | copier.CopyWithOption(employee, user, copier.Option{Must: true}) 76 | }) 77 | 78 | t.Run("must is false", func(t *testing.T) { 79 | defer func() { 80 | if r := recover(); r != nil { 81 | t.Error("Expected no panic.") 82 | } 83 | }() 84 | copier.CopyWithOption(employee, user, copier.Option{Must: false}) 85 | }) 86 | } 87 | 88 | func TestCopyTagMustAndNoPanic(t *testing.T) { 89 | employee := &EmployeeTags2{} 90 | user := &User2{DOB: "1 January 1970"} 91 | err := copier.Copy(employee, user) 92 | if err == nil { 93 | t.Error("expected error") 94 | } 95 | } 96 | 97 | func TestCopyTagMustAndNoPanicByOption(t *testing.T) { 98 | employee := &EmployeeTags3{} 99 | user := &User2{DOB: "1 January 1970"} 100 | err := copier.CopyWithOption(employee, user, copier.Option{Must: true, NoPanic: true}) 101 | if err == nil { 102 | t.Error("expected error") 103 | } 104 | } 105 | 106 | func TestCopyTagOverrideZeroValue(t *testing.T) { 107 | options := copier.Option{IgnoreEmpty: true} 108 | employee := EmployeeTags{ID: 100, Address: ""} 109 | user := User1{Name: "Dexter Ledesma", DOB: "1 November, 1970", Address: "21 Jump Street", ID: 12345} 110 | 111 | copier.CopyWithOption(&user, employee, options) 112 | if user.Address != "" { 113 | t.Error("Original Address was not overwritten") 114 | } 115 | } 116 | 117 | func TestCopyTagOverridePtrToZeroValue(t *testing.T) { 118 | options := copier.Option{IgnoreEmpty: true} 119 | address := "21 Jump Street" 120 | user1 := User1{ID: 100, Address: ""} 121 | user2 := User2{DOB: "1 November, 1970", Address: &address, ID: 12345} 122 | 123 | copier.CopyWithOption(&user2, user1, options) 124 | if user2.Address != nil { 125 | t.Error("Original Address was not overwritten") 126 | } 127 | } 128 | 129 | func TestCopyTagOverrideZeroValueToPtr(t *testing.T) { 130 | options := copier.Option{IgnoreEmpty: true} 131 | user1 := User2{DOB: "1 November, 1970", Address: nil, ID: 12345} 132 | user2 := User1{ID: 100, Address: "1 November, 1970"} 133 | 134 | copier.CopyWithOption(&user2, user1, options) 135 | if user1.Address != nil { 136 | t.Error("Original Address was not overwritten") 137 | } 138 | } 139 | 140 | func TestCopyTagOverridePtr(t *testing.T) { 141 | options := copier.Option{IgnoreEmpty: true} 142 | address := "21 Jump Street" 143 | user2 := User2{ID: 100, Address: nil} 144 | user := User2{DOB: "1 November, 1970", Address: &address, ID: 12345} 145 | 146 | copier.CopyWithOption(&user, user2, options) 147 | if user.Address != nil { 148 | t.Error("Original Address was not overwritten") 149 | } 150 | } 151 | 152 | func TestCopyTagFieldName(t *testing.T) { 153 | t.Run("another name field copy", func(t *testing.T) { 154 | type SrcTags struct { 155 | FieldA string 156 | FieldB string `copier:"Field2"` 157 | FieldC string `copier:"FieldTagMatch"` 158 | } 159 | 160 | type DestTags struct { 161 | Field1 string `copier:"FieldA"` 162 | Field2 string 163 | Field3 string `copier:"FieldTagMatch"` 164 | } 165 | 166 | dst := &DestTags{} 167 | src := &SrcTags{ 168 | FieldA: "FieldA->Field1", 169 | FieldB: "FieldB->Field2", 170 | FieldC: "FieldC->Field3", 171 | } 172 | err := copier.Copy(dst, src) 173 | if err != nil { 174 | t.Fatal(err) 175 | } 176 | 177 | if dst.Field1 != src.FieldA { 178 | t.Error("Field1 no copy") 179 | } 180 | if dst.Field2 != src.FieldB { 181 | t.Error("Field2 no copy") 182 | } 183 | if dst.Field3 != src.FieldC { 184 | t.Error("Field3 no copy") 185 | } 186 | }) 187 | 188 | t.Run("validate error flag name", func(t *testing.T) { 189 | type SrcTags struct { 190 | field string 191 | } 192 | 193 | type DestTags struct { 194 | Field1 string `copier:"field"` 195 | } 196 | 197 | dst := &DestTags{} 198 | src := &SrcTags{ 199 | field: "field->Field1", 200 | } 201 | err := copier.Copy(dst, src) 202 | if err == nil { 203 | t.Fatal("must validate error") 204 | } 205 | }) 206 | } 207 | -------------------------------------------------------------------------------- /copier_converter_test.go: -------------------------------------------------------------------------------- 1 | package copier_test 2 | 3 | import ( 4 | "bytes" 5 | "database/sql/driver" 6 | "encoding/json" 7 | "errors" 8 | "reflect" 9 | "strconv" 10 | "testing" 11 | "time" 12 | 13 | "github.com/jinzhu/copier" 14 | ) 15 | 16 | func TestCopyWithTypeConverters(t *testing.T) { 17 | type SrcStruct struct { 18 | Field1 time.Time 19 | Field2 *time.Time 20 | Field3 *time.Time 21 | Field4 string 22 | } 23 | 24 | type DestStruct struct { 25 | Field1 string 26 | Field2 string 27 | Field3 string 28 | Field4 int 29 | } 30 | 31 | testTime := time.Date(2021, 3, 5, 1, 30, 0, 123000000, time.UTC) 32 | 33 | src := SrcStruct{ 34 | Field1: testTime, 35 | Field2: &testTime, 36 | Field3: nil, 37 | Field4: "9000", 38 | } 39 | 40 | var dst DestStruct 41 | 42 | err := copier.CopyWithOption(&dst, &src, copier.Option{ 43 | IgnoreEmpty: true, 44 | DeepCopy: true, 45 | Converters: []copier.TypeConverter{ 46 | { 47 | SrcType: time.Time{}, 48 | DstType: copier.String, 49 | Fn: func(src interface{}) (interface{}, error) { 50 | s, ok := src.(time.Time) 51 | 52 | if !ok { 53 | return nil, errors.New("src type not matching") 54 | } 55 | 56 | return s.Format(time.RFC3339), nil 57 | }, 58 | }, 59 | { 60 | SrcType: copier.String, 61 | DstType: copier.Int, 62 | Fn: func(src interface{}) (interface{}, error) { 63 | s, ok := src.(string) 64 | 65 | if !ok { 66 | return nil, errors.New("src type not matching") 67 | } 68 | 69 | return strconv.Atoi(s) 70 | }, 71 | }, 72 | }, 73 | }) 74 | if err != nil { 75 | t.Fatalf(`Should be able to copy from src to dst object. %v`, err) 76 | return 77 | } 78 | 79 | dateStr := "2021-03-05T01:30:00Z" 80 | 81 | if dst.Field1 != dateStr { 82 | t.Fatalf("got %q, wanted %q", dst.Field1, dateStr) 83 | } 84 | 85 | if dst.Field2 != dateStr { 86 | t.Fatalf("got %q, wanted %q", dst.Field2, dateStr) 87 | } 88 | 89 | if dst.Field3 != "" { 90 | t.Fatalf("got %q, wanted %q", dst.Field3, "") 91 | } 92 | 93 | if dst.Field4 != 9000 { 94 | t.Fatalf("got %q, wanted %q", dst.Field4, 9000) 95 | } 96 | } 97 | 98 | func TestCopyWithConverterAndAnnotation(t *testing.T) { 99 | type SrcStruct struct { 100 | Field1 string 101 | } 102 | 103 | type DestStruct struct { 104 | Field1 string 105 | Field2 string `copier:"Field1"` 106 | } 107 | 108 | src := SrcStruct{ 109 | Field1: "test", 110 | } 111 | 112 | var dst DestStruct 113 | 114 | err := copier.CopyWithOption(&dst, &src, copier.Option{ 115 | IgnoreEmpty: true, 116 | DeepCopy: true, 117 | Converters: []copier.TypeConverter{ 118 | { 119 | SrcType: copier.String, 120 | DstType: copier.String, 121 | Fn: func(src interface{}) (interface{}, error) { 122 | s, ok := src.(string) 123 | 124 | if !ok { 125 | return nil, errors.New("src type not matching") 126 | } 127 | 128 | return s + "2", nil 129 | }, 130 | }, 131 | }, 132 | }) 133 | if err != nil { 134 | t.Fatalf(`Should be able to copy from src to dst object. %v`, err) 135 | return 136 | } 137 | 138 | if dst.Field2 != "test2" { 139 | t.Fatalf("got %q, wanted %q", dst.Field2, "test2") 140 | } 141 | } 142 | 143 | func TestCopyWithConverterStrToStrPointer(t *testing.T) { 144 | type SrcStruct struct { 145 | Field1 string 146 | } 147 | 148 | type DestStruct struct { 149 | Field1 *string 150 | } 151 | 152 | src := SrcStruct{ 153 | Field1: "", 154 | } 155 | 156 | var dst DestStruct 157 | 158 | ptrStrType := "" 159 | 160 | err := copier.CopyWithOption(&dst, &src, copier.Option{ 161 | IgnoreEmpty: true, 162 | DeepCopy: true, 163 | Converters: []copier.TypeConverter{ 164 | { 165 | SrcType: copier.String, 166 | DstType: &ptrStrType, 167 | Fn: func(src interface{}) (interface{}, error) { 168 | s, _ := src.(string) 169 | 170 | // return nil on empty string 171 | if s == "" { 172 | return nil, nil 173 | } 174 | 175 | return &s, nil 176 | }, 177 | }, 178 | }, 179 | }) 180 | if err != nil { 181 | t.Fatalf(`Should be able to copy from src to dst object. %v`, err) 182 | return 183 | } 184 | 185 | if dst.Field1 != nil { 186 | t.Fatalf("got %q, wanted nil", *dst.Field1) 187 | } 188 | } 189 | 190 | func TestCopyWithConverterRaisingError(t *testing.T) { 191 | type SrcStruct struct { 192 | Field1 string 193 | } 194 | 195 | type DestStruct struct { 196 | Field1 *string 197 | } 198 | 199 | src := SrcStruct{ 200 | Field1: "", 201 | } 202 | 203 | var dst DestStruct 204 | 205 | ptrStrType := "" 206 | 207 | err := copier.CopyWithOption(&dst, &src, copier.Option{ 208 | IgnoreEmpty: false, 209 | DeepCopy: true, 210 | Converters: []copier.TypeConverter{ 211 | { 212 | SrcType: copier.String, 213 | DstType: &ptrStrType, 214 | Fn: func(src interface{}) (interface{}, error) { 215 | return nil, errors.New("src type not matching") 216 | }, 217 | }, 218 | }, 219 | }) 220 | if err == nil { 221 | t.Fatalf(`Should be raising an error.`) 222 | return 223 | } 224 | } 225 | 226 | type IntArray []int 227 | 228 | func (a IntArray) Value() (driver.Value, error) { 229 | return json.Marshal(a) 230 | } 231 | 232 | type Int int 233 | 234 | type From struct { 235 | Data IntArray 236 | } 237 | 238 | type To struct { 239 | Data []byte 240 | } 241 | 242 | type FailedTo struct { 243 | Data []Int 244 | } 245 | 246 | func TestValuerConv(t *testing.T) { 247 | // when the field of struct implement driver.Valuer and cannot convert to dest type directly, 248 | // copier.set() will return a unexpected (true, nil) 249 | 250 | typ1 := reflect.TypeOf(IntArray{}) 251 | typ2 := reflect.TypeOf([]Int{}) 252 | 253 | if typ1 == typ2 || typ1.ConvertibleTo(typ2) || typ1.AssignableTo(typ2) { 254 | // in 1.22 and older, u can not convert typ1 to typ2 255 | t.Errorf("can not convert %v to %v direct", typ1, typ2) 256 | } 257 | 258 | var ( 259 | from = From{ 260 | Data: IntArray{1, 2, 3}, 261 | } 262 | to To 263 | failedTo FailedTo 264 | ) 265 | if err := copier.Copy(&to, from); err != nil { 266 | t.Fatal(err) 267 | } 268 | if err := copier.Copy(&failedTo, from); err != nil { 269 | t.Fatal(err) 270 | } 271 | 272 | // Testcase1: valuer conv case 273 | if !bytes.Equal(to.Data, []byte(`[1,2,3]`)) { 274 | t.Errorf("can not convert %v to %v using valuer", typ1, typ2) 275 | } 276 | 277 | // Testcase2: fallback case when valuer conv failed 278 | if len(failedTo.Data) != 3 || failedTo.Data[0] != 1 || failedTo.Data[1] != 2 || failedTo.Data[2] != 3 { 279 | t.Errorf("copier failed from %#v to %#v", from, failedTo) 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Copier 2 | 3 | I am a copier, I copy everything from one to another 4 | 5 | [![test status](https://github.com/jinzhu/copier/workflows/tests/badge.svg?branch=master "test status")](https://github.com/jinzhu/copier/actions) 6 | 7 | ## Key Features 8 | 9 | - Field-to-field and method-to-field copying based on matching names 10 | - Support for copying data: 11 | - From slice to slice 12 | - From struct to slice 13 | - From map to map 14 | - Field manipulation through tags: 15 | - Enforce field copying with `copier:"must"` 16 | - Override fields even when `IgnoreEmpty` is set with `copier:"override"` 17 | - Exclude fields from being copied with `copier:"-"` 18 | 19 | ## Getting Started 20 | 21 | ### Installation 22 | 23 | To start using Copier, install Go and run go get: 24 | 25 | ```bash 26 | go get -u github.com/jinzhu/copier 27 | ``` 28 | 29 | ## Basic 30 | 31 | Import Copier into your application to access its copying capabilities 32 | 33 | ```go 34 | import "github.com/jinzhu/copier" 35 | ``` 36 | 37 | ### Basic Copying 38 | 39 | ```go 40 | type User struct { 41 | Name string 42 | Role string 43 | Age int32 44 | } 45 | 46 | func (user *User) DoubleAge() int32 { 47 | return 2 * user.Age 48 | } 49 | 50 | type Employee struct { 51 | Name string 52 | Age int32 53 | DoubleAge int32 54 | SuperRole string 55 | } 56 | 57 | func (employee *Employee) Role(role string) { 58 | employee.SuperRole = "Super " + role 59 | } 60 | 61 | func main() { 62 | user := User{Name: "Jinzhu", Age: 18, Role: "Admin"} 63 | employee := Employee{} 64 | 65 | copier.Copy(&employee, &user) 66 | fmt.Printf("%#v\n", employee) 67 | // Output: Employee{Name:"Jinzhu", Age:18, DoubleAge:36, SuperRole:"Super Admin"} 68 | } 69 | ``` 70 | 71 | ## Tag Usage Examples 72 | 73 | ### `copier:"-"` - Ignoring Fields 74 | 75 | Fields tagged with `copier:"-"` are explicitly ignored by Copier during the copying process. 76 | 77 | ```go 78 | type Source struct { 79 | Name string 80 | Secret string // We do not want this to be copied. 81 | } 82 | 83 | type Target struct { 84 | Name string 85 | Secret string `copier:"-"` 86 | } 87 | 88 | func main() { 89 | source := Source{Name: "John", Secret: "so_secret"} 90 | target := Target{} 91 | 92 | copier.Copy(&target, &source) 93 | fmt.Printf("Name: %s, Secret: '%s'\n", target.Name, target.Secret) 94 | // Output: Name: John, Secret: '' 95 | } 96 | ``` 97 | 98 | ### `copier:"must"` - Enforcing Field Copy 99 | 100 | The `copier:"must"` tag forces a field to be copied, resulting in a panic or an error if the field cannot be copied. 101 | 102 | ```go 103 | type MandatorySource struct { 104 | Identification int 105 | } 106 | 107 | type MandatoryTarget struct { 108 | ID int `copier:"must"` // This field must be copied, or it will panic/error. 109 | } 110 | 111 | func main() { 112 | source := MandatorySource{} 113 | target := MandatoryTarget{ID: 10} 114 | 115 | // This will result in a panic or an error since ID is a must field but is empty in source. 116 | if err := copier.Copy(&target, &source); err != nil { 117 | log.Fatal(err) 118 | } 119 | } 120 | ``` 121 | 122 | ### `copier:"must,nopanic"` - Enforcing Field Copy Without Panic 123 | 124 | Similar to `copier:"must"`, but Copier returns an error instead of panicking if the field is not copied. 125 | 126 | ```go 127 | type SafeSource struct { 128 | ID string 129 | } 130 | 131 | type SafeTarget struct { 132 | Code string `copier:"must,nopanic"` // Enforce copying without panic. 133 | } 134 | 135 | func main() { 136 | source := SafeSource{} 137 | target := SafeTarget{Code: "200"} 138 | 139 | if err := copier.Copy(&target, &source); err != nil { 140 | log.Fatalln("Error:", err) 141 | } 142 | // This will not panic, but will return an error due to missing mandatory field. 143 | } 144 | ``` 145 | 146 | ### `copier:"override"` - Overriding Fields with IgnoreEmpty 147 | 148 | Fields tagged with `copier:"override"` are copied even if IgnoreEmpty is set to true in Copier options and works for nil values. 149 | 150 | ```go 151 | type SourceWithNil struct { 152 | Details *string 153 | } 154 | 155 | type TargetOverride struct { 156 | Details *string `copier:"override"` // Even if source is nil, copy it. 157 | } 158 | 159 | func main() { 160 | details := "Important details" 161 | source := SourceWithNil{Details: nil} 162 | target := TargetOverride{Details: &details} 163 | 164 | copier.CopyWithOption(&target, &source, copier.Option{IgnoreEmpty: true}) 165 | if target.Details == nil { 166 | fmt.Println("Details field was overridden to nil.") 167 | } 168 | } 169 | ``` 170 | 171 | ### Specifying Custom Field Names 172 | 173 | Use field tags to specify a custom field name when the source and destination field names do not match. 174 | 175 | ```go 176 | type SourceEmployee struct { 177 | Identifier int64 178 | } 179 | 180 | type TargetWorker struct { 181 | ID int64 `copier:"Identifier"` // Map Identifier from SourceEmployee to ID in TargetWorker 182 | } 183 | 184 | func main() { 185 | source := SourceEmployee{Identifier: 1001} 186 | target := TargetWorker{} 187 | 188 | copier.Copy(&target, &source) 189 | fmt.Printf("Worker ID: %d\n", target.ID) 190 | // Output: Worker ID: 1001 191 | } 192 | ``` 193 | 194 | ## Other examples 195 | 196 | ### Copy from Method to Field with Same Name 197 | 198 | Illustrates copying from a method to a field and vice versa. 199 | 200 | ```go 201 | // Assuming User and Employee structs defined earlier with method and field respectively. 202 | 203 | func main() { 204 | user := User{Name: "Jinzhu", Age: 18} 205 | employee := Employee{} 206 | 207 | copier.Copy(&employee, &user) 208 | fmt.Printf("DoubleAge: %d\n", employee.DoubleAge) 209 | // Output: DoubleAge: 36, demonstrating method to field copying. 210 | } 211 | ``` 212 | 213 | ### Copy Struct to Slice 214 | 215 | ```go 216 | func main() { 217 | user := User{Name: "Jinzhu", Age: 18, Role: "Admin"} 218 | var employees []Employee 219 | 220 | copier.Copy(&employees, &user) 221 | fmt.Printf("%#v\n", employees) 222 | // Output: []Employee{{Name: "Jinzhu", Age: 18, DoubleAge: 36, SuperRole: "Super Admin"}} 223 | } 224 | ``` 225 | 226 | ### Copy Slice to Slice 227 | 228 | ```go 229 | func main() { 230 | users := []User{{Name: "Jinzhu", Age: 18, Role: "Admin"}, {Name: "jinzhu 2", Age: 30, Role: "Dev"}} 231 | var employees []Employee 232 | 233 | copier.Copy(&employees, &users) 234 | fmt.Printf("%#v\n", employees) 235 | // Output: []Employee{{Name: "Jinzhu", Age: 18, DoubleAge: 36, SuperRole: "Super Admin"}, {Name: "jinzhu 2", Age: 30, DoubleAge: 60, SuperRole: "Super Dev"}} 236 | } 237 | ``` 238 | 239 | ### Copy Map to Map 240 | 241 | ```go 242 | func main() { 243 | map1 := map[int]int{3: 6, 4: 8} 244 | map2 := map[int32]int8{} 245 | 246 | copier.Copy(&map2, map1) 247 | fmt.Printf("%#v\n", map2) 248 | // Output: map[int32]int8{3:6, 4:8} 249 | } 250 | ``` 251 | 252 | ## Complex Data Copying: Nested Structures with Slices 253 | 254 | This example demonstrates how Copier can be used to copy data involving complex, nested structures, including slices of structs, to showcase its ability to handle intricate data copying scenarios. 255 | 256 | ```go 257 | package main 258 | 259 | import ( 260 | "fmt" 261 | "github.com/jinzhu/copier" 262 | ) 263 | 264 | type Address struct { 265 | City string 266 | Country string 267 | } 268 | 269 | type Contact struct { 270 | Email string 271 | Phones []string 272 | } 273 | 274 | type Employee struct { 275 | Name string 276 | Age int32 277 | Addresses []Address 278 | Contact *Contact 279 | } 280 | 281 | type Manager struct { 282 | Name string `copier:"must"` 283 | Age int32 `copier:"must,nopanic"` 284 | ManagedCities []string 285 | Contact *Contact `copier:"override"` 286 | SecondaryEmails []string 287 | } 288 | 289 | func main() { 290 | employee := Employee{ 291 | Name: "John Doe", 292 | Age: 30, 293 | Addresses: []Address{ 294 | {City: "New York", Country: "USA"}, 295 | {City: "San Francisco", Country: "USA"}, 296 | }, 297 | Contact: nil, 298 | } 299 | 300 | manager := Manager{ 301 | ManagedCities: []string{"Los Angeles", "Boston"}, 302 | Contact: &Contact{ 303 | Email: "john.doe@example.com", 304 | Phones: []string{"123-456-7890", "098-765-4321"}, 305 | }, // since override is set this should be overridden with nil 306 | SecondaryEmails: []string{"secondary@example.com"}, 307 | } 308 | 309 | copier.CopyWithOption(&manager, &employee, copier.Option{IgnoreEmpty: true, DeepCopy: true}) 310 | 311 | fmt.Printf("Manager: %#v\n", manager) 312 | // Output: Manager struct showcasing copied fields from Employee, 313 | // including overridden and deeply copied nested slices. 314 | } 315 | ``` 316 | 317 | ## Available tags 318 | 319 | | Tag | Description | 320 | | ------------------- | ----------------------------------------------------------------------------------------------------------------- | 321 | | `copier:"-"` | Explicitly ignores the field during copying. | 322 | | `copier:"must"` | Forces the field to be copied; Copier will panic or return an error if the field is not copied. | 323 | | `copier:"nopanic"` | Copier will return an error instead of panicking. | 324 | | `copier:"override"` | Forces the field to be copied even if `IgnoreEmpty` is set. Useful for overriding existing values with empty ones | 325 | | `FieldName` | Specifies a custom field name for copying when field names do not match between structs. | 326 | 327 | ## Contributing 328 | 329 | You can help to make the project better, check out [http://gorm.io/contribute.html](http://gorm.io/contribute.html) for things you can do. 330 | 331 | # Author 332 | 333 | **jinzhu** 334 | 335 | - 336 | - 337 | - 338 | 339 | ## License 340 | 341 | Released under the [MIT License](https://github.com/jinzhu/copier/blob/master/License). 342 | -------------------------------------------------------------------------------- /copier.go: -------------------------------------------------------------------------------- 1 | package copier 2 | 3 | import ( 4 | "database/sql" 5 | "database/sql/driver" 6 | "fmt" 7 | "reflect" 8 | "strings" 9 | "sync" 10 | "unicode" 11 | ) 12 | 13 | // These flags define options for tag handling 14 | const ( 15 | // Denotes that a destination field must be copied to. If copying fails then a panic will ensue. 16 | tagMust uint8 = 1 << iota 17 | 18 | // Denotes that the program should not panic when the must flag is on and 19 | // value is not copied. The program will return an error instead. 20 | tagNoPanic 21 | 22 | // Ignore a destination field from being copied to. 23 | tagIgnore 24 | 25 | // Denotes the fact that the field should be overridden, no matter if the IgnoreEmpty is set 26 | tagOverride 27 | 28 | // Denotes that the value as been copied 29 | hasCopied 30 | 31 | // Some default converter types for a nicer syntax 32 | String string = "" 33 | Bool bool = false 34 | Int int = 0 35 | Float32 float32 = 0 36 | Float64 float64 = 0 37 | ) 38 | 39 | // Option sets copy options 40 | type Option struct { 41 | // setting this value to true will ignore copying zero values of all the fields, including bools, as well as a 42 | // struct having all it's fields set to their zero values respectively (see IsZero() in reflect/value.go) 43 | IgnoreEmpty bool 44 | CaseSensitive bool 45 | DeepCopy bool 46 | Converters []TypeConverter 47 | // Custom field name mappings to copy values with different names in `fromValue` and `toValue` types. 48 | // Examples can be found in `copier_field_name_mapping_test.go`. 49 | FieldNameMapping []FieldNameMapping 50 | // When set to true, all fields will be treated as if they have the "must" tag 51 | Must bool 52 | // When set to true, all fields will be treated as if they have the "nopanic" tag. 53 | // This only has an effect when Must is also true 54 | NoPanic bool 55 | } 56 | 57 | func (opt Option) converters() map[converterPair]TypeConverter { 58 | var converters = map[converterPair]TypeConverter{} 59 | 60 | // save converters into map for faster lookup 61 | for i := range opt.Converters { 62 | pair := converterPair{ 63 | SrcType: reflect.TypeOf(opt.Converters[i].SrcType), 64 | DstType: reflect.TypeOf(opt.Converters[i].DstType), 65 | } 66 | 67 | converters[pair] = opt.Converters[i] 68 | } 69 | 70 | return converters 71 | } 72 | 73 | type TypeConverter struct { 74 | SrcType interface{} 75 | DstType interface{} 76 | Fn func(src interface{}) (dst interface{}, err error) 77 | } 78 | 79 | type converterPair struct { 80 | SrcType reflect.Type 81 | DstType reflect.Type 82 | } 83 | 84 | func (opt Option) fieldNameMapping() map[converterPair]FieldNameMapping { 85 | var mapping = map[converterPair]FieldNameMapping{} 86 | 87 | for i := range opt.FieldNameMapping { 88 | pair := converterPair{ 89 | SrcType: reflect.TypeOf(opt.FieldNameMapping[i].SrcType), 90 | DstType: reflect.TypeOf(opt.FieldNameMapping[i].DstType), 91 | } 92 | 93 | mapping[pair] = opt.FieldNameMapping[i] 94 | } 95 | 96 | return mapping 97 | } 98 | 99 | type FieldNameMapping struct { 100 | SrcType interface{} 101 | DstType interface{} 102 | Mapping map[string]string 103 | } 104 | 105 | // Tag Flags 106 | type flags struct { 107 | BitFlags map[string]uint8 108 | SrcNames tagNameMapping 109 | DestNames tagNameMapping 110 | } 111 | 112 | // Field Tag name mapping 113 | type tagNameMapping struct { 114 | FieldNameToTag map[string]string 115 | TagToFieldName map[string]string 116 | } 117 | 118 | // Copy copy things 119 | func Copy(toValue interface{}, fromValue interface{}) (err error) { 120 | return copier(toValue, fromValue, Option{}) 121 | } 122 | 123 | // CopyWithOption copy with option 124 | func CopyWithOption(toValue interface{}, fromValue interface{}, opt Option) (err error) { 125 | return copier(toValue, fromValue, opt) 126 | } 127 | 128 | func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) { 129 | var ( 130 | isSlice bool 131 | amount = 1 132 | from = indirect(reflect.ValueOf(fromValue)) 133 | to = indirect(reflect.ValueOf(toValue)) 134 | converters = opt.converters() 135 | mappings = opt.fieldNameMapping() 136 | ) 137 | 138 | if !to.CanAddr() { 139 | return ErrInvalidCopyDestination 140 | } 141 | 142 | // Return is from value is invalid 143 | if !from.IsValid() { 144 | return ErrInvalidCopyFrom 145 | } 146 | 147 | fromType, isPtrFrom := indirectType(from.Type()) 148 | toType, _ := indirectType(to.Type()) 149 | 150 | if fromType.Kind() == reflect.Interface { 151 | fromType = reflect.TypeOf(from.Interface()) 152 | } 153 | 154 | if toType.Kind() == reflect.Interface { 155 | toType, _ = indirectType(reflect.TypeOf(to.Interface())) 156 | oldTo := to 157 | to = reflect.New(reflect.TypeOf(to.Interface())).Elem() 158 | defer func() { 159 | oldTo.Set(to) 160 | }() 161 | } 162 | 163 | // Just set it if possible to assign for normal types 164 | if from.Kind() != reflect.Slice && from.Kind() != reflect.Struct && from.Kind() != reflect.Map && (from.Type().AssignableTo(to.Type()) || from.Type().ConvertibleTo(to.Type())) { 165 | if !isPtrFrom || !opt.DeepCopy { 166 | to.Set(from.Convert(to.Type())) 167 | } else { 168 | fromCopy := reflect.New(from.Type()) 169 | fromCopy.Set(from.Elem()) 170 | to.Set(fromCopy.Convert(to.Type())) 171 | } 172 | return 173 | } 174 | 175 | if from.Kind() != reflect.Slice && fromType.Kind() == reflect.Map && toType.Kind() == reflect.Map { 176 | if !fromType.Key().ConvertibleTo(toType.Key()) { 177 | return ErrMapKeyNotMatch 178 | } 179 | 180 | if to.IsNil() { 181 | to.Set(reflect.MakeMapWithSize(toType, from.Len())) 182 | } 183 | 184 | for _, k := range from.MapKeys() { 185 | toKey := indirect(reflect.New(toType.Key())) 186 | isSet, err := set(toKey, k, opt.DeepCopy, converters) 187 | if err != nil { 188 | return err 189 | } 190 | if !isSet { 191 | return fmt.Errorf("%w map, old key: %v, new key: %v", ErrNotSupported, k.Type(), toType.Key()) 192 | } 193 | 194 | elemType := toType.Elem() 195 | if elemType.Kind() != reflect.Slice { 196 | elemType, _ = indirectType(elemType) 197 | } 198 | toValue := indirect(reflect.New(elemType)) 199 | isSet, err = set(toValue, from.MapIndex(k), opt.DeepCopy, converters) 200 | if err != nil { 201 | return err 202 | } 203 | if !isSet { 204 | if err = copier(toValue.Addr().Interface(), from.MapIndex(k).Interface(), opt); err != nil { 205 | return err 206 | } 207 | } 208 | 209 | for { 210 | if elemType == toType.Elem() { 211 | to.SetMapIndex(toKey, toValue) 212 | break 213 | } 214 | elemType = reflect.PointerTo(elemType) 215 | toValue = toValue.Addr() 216 | } 217 | } 218 | return 219 | } 220 | 221 | if from.Kind() == reflect.Slice && to.Kind() == reflect.Slice { 222 | // Return directly if both slices are nil 223 | if from.IsNil() && to.IsNil() { 224 | return 225 | } 226 | if to.IsNil() { 227 | slice := reflect.MakeSlice(reflect.SliceOf(to.Type().Elem()), from.Len(), from.Cap()) 228 | to.Set(slice) 229 | } 230 | if fromType.ConvertibleTo(toType) { 231 | for i := 0; i < from.Len(); i++ { 232 | if to.Len() < i+1 { 233 | to.Set(reflect.Append(to, reflect.New(to.Type().Elem()).Elem())) 234 | } 235 | isSet, err := set(to.Index(i), from.Index(i), opt.DeepCopy, converters) 236 | if err != nil { 237 | return err 238 | } 239 | if !isSet { 240 | // ignore error while copy slice element 241 | err = copier(to.Index(i).Addr().Interface(), from.Index(i).Interface(), opt) 242 | if err != nil { 243 | continue 244 | } 245 | } 246 | } 247 | 248 | if to.Len() > from.Len() { 249 | to.SetLen(from.Len()) 250 | } 251 | 252 | return 253 | } 254 | } 255 | 256 | if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct { 257 | // skip not supported type 258 | return 259 | } 260 | 261 | if len(converters) > 0 { 262 | if ok, e := set(to, from, opt.DeepCopy, converters); e == nil && ok { 263 | // converter supported 264 | return 265 | } 266 | } 267 | 268 | if from.Kind() == reflect.Slice || to.Kind() == reflect.Slice { 269 | isSlice = true 270 | if from.Kind() == reflect.Slice { 271 | amount = from.Len() 272 | } 273 | } 274 | 275 | for i := 0; i < amount; i++ { 276 | var dest, source reflect.Value 277 | 278 | if isSlice { 279 | // source 280 | if from.Kind() == reflect.Slice { 281 | source = indirect(from.Index(i)) 282 | } else { 283 | source = indirect(from) 284 | } 285 | // dest 286 | dest = indirect(reflect.New(toType).Elem()) 287 | } else { 288 | source = indirect(from) 289 | dest = indirect(to) 290 | } 291 | 292 | if len(converters) > 0 { 293 | if ok, e := set(dest, source, opt.DeepCopy, converters); e == nil && ok { 294 | if isSlice { 295 | // FIXME: maybe should check the other types? 296 | if to.Type().Elem().Kind() == reflect.Ptr { 297 | to.Index(i).Set(dest.Addr()) 298 | } else { 299 | if to.Len() < i+1 { 300 | reflect.Append(to, dest) 301 | } else { 302 | to.Index(i).Set(dest) 303 | } 304 | } 305 | } else { 306 | to.Set(dest) 307 | } 308 | 309 | continue 310 | } 311 | } 312 | 313 | destKind := dest.Kind() 314 | initDest := false 315 | if destKind == reflect.Interface { 316 | initDest = true 317 | dest = indirect(reflect.New(toType)) 318 | } 319 | 320 | var flgs flags 321 | flgs, err = getFlags(dest, source, toType, fromType, opt) 322 | if err != nil { 323 | return err 324 | } 325 | 326 | // check source 327 | if source.IsValid() { 328 | copyUnexportedStructFields(dest, source) 329 | 330 | // Copy from source field to dest field or method 331 | fromTypeFields := deepFields(fromType) 332 | for _, field := range fromTypeFields { 333 | name := field.Name 334 | 335 | // Get bit flags for field 336 | fieldFlags := flgs.BitFlags[name] 337 | 338 | // Check if we should ignore copying 339 | if (fieldFlags & tagIgnore) != 0 { 340 | continue 341 | } 342 | 343 | fieldNamesMapping := getFieldNamesMapping(mappings, fromType, toType) 344 | 345 | srcFieldName, destFieldName := getFieldName(name, flgs, fieldNamesMapping) 346 | 347 | if fromField := fieldByNameOrZeroValue(source, srcFieldName); fromField.IsValid() && !shouldIgnore(fromField, fieldFlags, opt.IgnoreEmpty) { 348 | // process for nested anonymous field 349 | destFieldNotSet := false 350 | if f, ok := dest.Type().FieldByName(destFieldName); ok { 351 | // only initialize parent embedded struct pointer in the path 352 | for idx := range f.Index[:len(f.Index)-1] { 353 | destField := dest.FieldByIndex(f.Index[:idx+1]) 354 | 355 | if destField.Kind() != reflect.Ptr { 356 | continue 357 | } 358 | 359 | if !destField.IsNil() { 360 | continue 361 | } 362 | if !destField.CanSet() { 363 | destFieldNotSet = true 364 | break 365 | } 366 | 367 | // destField is a nil pointer that can be set 368 | newValue := reflect.New(destField.Type().Elem()) 369 | destField.Set(newValue) 370 | } 371 | } 372 | 373 | if destFieldNotSet { 374 | break 375 | } 376 | 377 | toField := fieldByName(dest, destFieldName, opt.CaseSensitive) 378 | if toField.IsValid() { 379 | if toField.CanSet() { 380 | isSet, err := set(toField, fromField, opt.DeepCopy, converters) 381 | if err != nil { 382 | return err 383 | } 384 | if !isSet { 385 | if err := copier(toField.Addr().Interface(), fromField.Interface(), opt); err != nil { 386 | return err 387 | } 388 | } 389 | if fieldFlags != 0 { 390 | // Note that a copy was made 391 | flgs.BitFlags[name] = fieldFlags | hasCopied 392 | } 393 | } 394 | } else { 395 | // try to set to method 396 | var toMethod reflect.Value 397 | if dest.CanAddr() { 398 | toMethod = dest.Addr().MethodByName(destFieldName) 399 | } else { 400 | toMethod = dest.MethodByName(destFieldName) 401 | } 402 | 403 | if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) { 404 | toMethod.Call([]reflect.Value{fromField}) 405 | } 406 | } 407 | } 408 | } 409 | 410 | // Copy from from method to dest field 411 | for _, field := range deepFields(toType) { 412 | name := field.Name 413 | srcFieldName, destFieldName := getFieldName(name, flgs, getFieldNamesMapping(mappings, fromType, toType)) 414 | 415 | var fromMethod reflect.Value 416 | if source.CanAddr() { 417 | fromMethod = source.Addr().MethodByName(srcFieldName) 418 | } else { 419 | fromMethod = source.MethodByName(srcFieldName) 420 | } 421 | 422 | if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, flgs.BitFlags[name], opt.IgnoreEmpty) { 423 | if toField := fieldByName(dest, destFieldName, opt.CaseSensitive); toField.IsValid() && toField.CanSet() { 424 | values := fromMethod.Call([]reflect.Value{}) 425 | if len(values) >= 1 { 426 | set(toField, values[0], opt.DeepCopy, converters) 427 | } 428 | } 429 | } 430 | } 431 | } 432 | 433 | if isSlice && to.Kind() == reflect.Slice { 434 | if dest.Addr().Type().AssignableTo(to.Type().Elem()) { 435 | if to.Len() < i+1 { 436 | to.Set(reflect.Append(to, dest.Addr())) 437 | } else { 438 | isSet, err := set(to.Index(i), dest.Addr(), opt.DeepCopy, converters) 439 | if err != nil { 440 | return err 441 | } 442 | if !isSet { 443 | // ignore error while copy slice element 444 | err = copier(to.Index(i).Addr().Interface(), dest.Addr().Interface(), opt) 445 | if err != nil { 446 | continue 447 | } 448 | } 449 | } 450 | } else if dest.Type().AssignableTo(to.Type().Elem()) { 451 | if to.Len() < i+1 { 452 | to.Set(reflect.Append(to, dest)) 453 | } else { 454 | isSet, err := set(to.Index(i), dest, opt.DeepCopy, converters) 455 | if err != nil { 456 | return err 457 | } 458 | if !isSet { 459 | // ignore error while copy slice element 460 | err = copier(to.Index(i).Addr().Interface(), dest.Interface(), opt) 461 | if err != nil { 462 | continue 463 | } 464 | } 465 | } 466 | } 467 | } else if initDest { 468 | to.Set(dest) 469 | } 470 | 471 | err = checkBitFlags(flgs.BitFlags) 472 | } 473 | 474 | return 475 | } 476 | 477 | func getFieldNamesMapping(mappings map[converterPair]FieldNameMapping, fromType reflect.Type, toType reflect.Type) map[string]string { 478 | var fieldNamesMapping map[string]string 479 | 480 | if len(mappings) > 0 { 481 | pair := converterPair{ 482 | SrcType: fromType, 483 | DstType: toType, 484 | } 485 | if v, ok := mappings[pair]; ok { 486 | fieldNamesMapping = v.Mapping 487 | } 488 | } 489 | return fieldNamesMapping 490 | } 491 | 492 | func fieldByNameOrZeroValue(source reflect.Value, fieldName string) (value reflect.Value) { 493 | defer func() { 494 | if err := recover(); err != nil { 495 | value = reflect.Value{} 496 | } 497 | }() 498 | 499 | return source.FieldByName(fieldName) 500 | } 501 | 502 | func copyUnexportedStructFields(to, from reflect.Value) { 503 | if from.Kind() != reflect.Struct || to.Kind() != reflect.Struct || !from.Type().AssignableTo(to.Type()) { 504 | return 505 | } 506 | 507 | // create a shallow copy of 'to' to get all fields 508 | tmp := indirect(reflect.New(to.Type())) 509 | tmp.Set(from) 510 | 511 | // revert exported fields 512 | for i := 0; i < to.NumField(); i++ { 513 | if tmp.Field(i).CanSet() { 514 | tmp.Field(i).Set(to.Field(i)) 515 | } 516 | } 517 | to.Set(tmp) 518 | } 519 | 520 | func shouldIgnore(v reflect.Value, bitFlags uint8, ignoreEmpty bool) bool { 521 | return ignoreEmpty && bitFlags&tagOverride == 0 && v.IsZero() 522 | } 523 | 524 | var deepFieldsLock sync.RWMutex 525 | var deepFieldsMap = make(map[reflect.Type][]reflect.StructField) 526 | 527 | func deepFields(reflectType reflect.Type) []reflect.StructField { 528 | deepFieldsLock.RLock() 529 | cache, ok := deepFieldsMap[reflectType] 530 | deepFieldsLock.RUnlock() 531 | if ok { 532 | return cache 533 | } 534 | var res []reflect.StructField 535 | if reflectType, _ = indirectType(reflectType); reflectType.Kind() == reflect.Struct { 536 | fields := make([]reflect.StructField, 0, reflectType.NumField()) 537 | 538 | for i := 0; i < reflectType.NumField(); i++ { 539 | v := reflectType.Field(i) 540 | // PkgPath is the package path that qualifies a lower case (unexported) 541 | // field name. It is empty for upper case (exported) field names. 542 | // See https://golang.org/ref/spec#Uniqueness_of_identifiers 543 | if v.PkgPath == "" { 544 | fields = append(fields, v) 545 | if v.Anonymous { 546 | // also consider fields of anonymous fields as fields of the root 547 | fields = append(fields, deepFields(v.Type)...) 548 | } 549 | } 550 | } 551 | res = fields 552 | } 553 | 554 | deepFieldsLock.Lock() 555 | deepFieldsMap[reflectType] = res 556 | deepFieldsLock.Unlock() 557 | return res 558 | } 559 | 560 | func indirect(reflectValue reflect.Value) reflect.Value { 561 | for reflectValue.Kind() == reflect.Ptr { 562 | reflectValue = reflectValue.Elem() 563 | } 564 | return reflectValue 565 | } 566 | 567 | func indirectType(reflectType reflect.Type) (_ reflect.Type, isPtr bool) { 568 | for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice { 569 | reflectType = reflectType.Elem() 570 | isPtr = true 571 | } 572 | return reflectType, isPtr 573 | } 574 | 575 | func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]TypeConverter) (bool, error) { 576 | if !from.IsValid() { 577 | return true, nil 578 | } 579 | if ok, err := lookupAndCopyWithConverter(to, from, converters); err != nil { 580 | return false, err 581 | } else if ok { 582 | return true, nil 583 | } 584 | 585 | if to.Kind() == reflect.Ptr { 586 | // set `to` to nil if from is nil 587 | if from.Kind() == reflect.Ptr && from.IsNil() { 588 | to.Set(reflect.Zero(to.Type())) 589 | return true, nil 590 | } else if to.IsNil() { 591 | // `from` -> `to` 592 | // sql.NullString -> *string 593 | if fromValuer, ok := driverValuer(from); ok { 594 | v, err := fromValuer.Value() 595 | if err != nil { 596 | return true, nil 597 | } 598 | // if `from` is not valid do nothing with `to` 599 | if v == nil { 600 | return true, nil 601 | } 602 | } 603 | // allocate new `to` variable with default value (eg. *string -> new(string)) 604 | to.Set(reflect.New(to.Type().Elem())) 605 | } else if from.Kind() != reflect.Ptr && from.IsZero() { 606 | to.Set(reflect.Zero(to.Type())) 607 | return true, nil 608 | } 609 | // depointer `to` 610 | to = to.Elem() 611 | } 612 | 613 | if deepCopy { 614 | toKind := to.Kind() 615 | if toKind == reflect.Interface && to.IsNil() { 616 | if reflect.TypeOf(from.Interface()) != nil { 617 | to.Set(reflect.New(reflect.TypeOf(from.Interface())).Elem()) 618 | toKind = reflect.TypeOf(to.Interface()).Kind() 619 | } 620 | } 621 | if from.Kind() == reflect.Ptr && from.IsNil() { 622 | to.Set(reflect.Zero(to.Type())) 623 | return true, nil 624 | } 625 | if _, ok := to.Addr().Interface().(sql.Scanner); !ok && (toKind == reflect.Struct || toKind == reflect.Map || toKind == reflect.Slice) { 626 | return false, nil 627 | } 628 | } 629 | 630 | // try convert directly 631 | if from.Type().ConvertibleTo(to.Type()) { 632 | to.Set(from.Convert(to.Type())) 633 | return true, nil 634 | } 635 | 636 | // try Scanner 637 | if toScanner, ok := to.Addr().Interface().(sql.Scanner); ok { 638 | // `from` -> `to` 639 | // *string -> sql.NullString 640 | if from.Kind() == reflect.Ptr { 641 | // if `from` is nil do nothing with `to` 642 | if from.IsNil() { 643 | return true, nil 644 | } 645 | // depointer `from` 646 | from = indirect(from) 647 | } 648 | // `from` -> `to` 649 | // string -> sql.NullString 650 | // set `to` by invoking method Scan(`from`) 651 | err := toScanner.Scan(from.Interface()) 652 | if err == nil { 653 | return true, nil 654 | } 655 | } 656 | 657 | // try Valuer 658 | if fromValuer, ok := driverValuer(from); ok { 659 | // `from` -> `to` 660 | // sql.NullString -> string 661 | v, err := fromValuer.Value() 662 | if err != nil { 663 | return false, nil 664 | } 665 | // if `from` is not valid do nothing with `to` 666 | if v == nil { 667 | return true, nil 668 | } 669 | rv := reflect.ValueOf(v) 670 | if rv.Type().AssignableTo(to.Type()) { 671 | to.Set(rv) 672 | return true, nil 673 | } 674 | if to.CanSet() && rv.Type().ConvertibleTo(to.Type()) { 675 | to.Set(rv.Convert(to.Type())) 676 | return true, nil 677 | } 678 | return false, nil 679 | } 680 | 681 | // from is ptr 682 | if from.Kind() == reflect.Ptr { 683 | return set(to, from.Elem(), deepCopy, converters) 684 | } 685 | 686 | return false, nil 687 | } 688 | 689 | // lookupAndCopyWithConverter looks up the type pair, on success the TypeConverter Fn func is called to copy src to dst field. 690 | func lookupAndCopyWithConverter(to, from reflect.Value, converters map[converterPair]TypeConverter) (copied bool, err error) { 691 | pair := converterPair{ 692 | SrcType: from.Type(), 693 | DstType: to.Type(), 694 | } 695 | 696 | if cnv, ok := converters[pair]; ok { 697 | result, err := cnv.Fn(from.Interface()) 698 | if err != nil { 699 | return false, err 700 | } 701 | 702 | if result != nil { 703 | to.Set(reflect.ValueOf(result)) 704 | } else { 705 | // in case we've got a nil value to copy 706 | to.Set(reflect.Zero(to.Type())) 707 | } 708 | 709 | return true, nil 710 | } 711 | 712 | return false, nil 713 | } 714 | 715 | // parseTags Parses struct tags and returns uint8 bit flags. 716 | func parseTags(tag string) (flg uint8, name string, err error) { 717 | for _, t := range strings.Split(tag, ",") { 718 | switch t { 719 | case "-": 720 | flg = tagIgnore 721 | return 722 | case "must": 723 | flg = flg | tagMust 724 | case "nopanic": 725 | flg = flg | tagNoPanic 726 | case "override": 727 | flg = flg | tagOverride 728 | default: 729 | if unicode.IsUpper([]rune(t)[0]) { 730 | name = strings.TrimSpace(t) 731 | } else { 732 | err = ErrFieldNameTagStartNotUpperCase 733 | } 734 | } 735 | } 736 | return 737 | } 738 | 739 | // getTagFlags Parses struct tags for bit flags, field name. 740 | func getFlags(dest, src reflect.Value, toType, fromType reflect.Type, opt Option) (flags, error) { 741 | flgs := flags{ 742 | BitFlags: map[string]uint8{}, 743 | SrcNames: tagNameMapping{ 744 | FieldNameToTag: map[string]string{}, 745 | TagToFieldName: map[string]string{}, 746 | }, 747 | DestNames: tagNameMapping{ 748 | FieldNameToTag: map[string]string{}, 749 | TagToFieldName: map[string]string{}, 750 | }, 751 | } 752 | 753 | mustOpt := opt.Must 754 | noPanicOpt := opt.NoPanic 755 | 756 | var toTypeFields, fromTypeFields []reflect.StructField 757 | if dest.IsValid() { 758 | toTypeFields = deepFields(toType) 759 | } 760 | if src.IsValid() { 761 | fromTypeFields = deepFields(fromType) 762 | } 763 | 764 | // Get a list dest of tags 765 | for _, field := range toTypeFields { 766 | tags := field.Tag.Get("copier") 767 | if tags != "" { 768 | var name string 769 | var err error 770 | if flgs.BitFlags[field.Name], name, err = parseTags(tags); err != nil { 771 | return flags{}, err 772 | } else if name != "" { 773 | flgs.DestNames.FieldNameToTag[field.Name] = name 774 | flgs.DestNames.TagToFieldName[name] = field.Name 775 | } 776 | } 777 | 778 | // Apply default flags 779 | if mustOpt { 780 | flgs.BitFlags[field.Name] = flgs.BitFlags[field.Name] | tagMust 781 | } 782 | if noPanicOpt { 783 | flgs.BitFlags[field.Name] = flgs.BitFlags[field.Name] | tagNoPanic 784 | } 785 | } 786 | 787 | // Get a list source of tags 788 | for _, field := range fromTypeFields { 789 | tags := field.Tag.Get("copier") 790 | if tags != "" { 791 | var name string 792 | var err error 793 | 794 | if _, name, err = parseTags(tags); err != nil { 795 | return flags{}, err 796 | } else if name != "" { 797 | flgs.SrcNames.FieldNameToTag[field.Name] = name 798 | flgs.SrcNames.TagToFieldName[name] = field.Name 799 | } 800 | } 801 | } 802 | 803 | return flgs, nil 804 | } 805 | 806 | // checkBitFlags Checks flags for error or panic conditions. 807 | func checkBitFlags(flagsList map[string]uint8) (err error) { 808 | // Check flag conditions were met 809 | for name, flgs := range flagsList { 810 | if flgs&hasCopied == 0 { 811 | switch { 812 | case flgs&tagMust != 0 && flgs&tagNoPanic != 0: 813 | err = fmt.Errorf("field %s has must tag but was not copied", name) 814 | return 815 | case flgs&(tagMust) != 0: 816 | panic(fmt.Sprintf("Field %s has must tag but was not copied", name)) 817 | } 818 | } 819 | } 820 | return 821 | } 822 | 823 | func getFieldName(fieldName string, flgs flags, fieldNameMapping map[string]string) (srcFieldName string, destFieldName string) { 824 | // get dest field name 825 | if name, ok := fieldNameMapping[fieldName]; ok { 826 | srcFieldName = fieldName 827 | destFieldName = name 828 | return 829 | } 830 | 831 | if srcTagName, ok := flgs.SrcNames.FieldNameToTag[fieldName]; ok { 832 | destFieldName = srcTagName 833 | if destTagName, ok := flgs.DestNames.TagToFieldName[srcTagName]; ok { 834 | destFieldName = destTagName 835 | } 836 | } else { 837 | if destTagName, ok := flgs.DestNames.TagToFieldName[fieldName]; ok { 838 | destFieldName = destTagName 839 | } 840 | } 841 | if destFieldName == "" { 842 | destFieldName = fieldName 843 | } 844 | 845 | // get source field name 846 | if destTagName, ok := flgs.DestNames.FieldNameToTag[fieldName]; ok { 847 | srcFieldName = destTagName 848 | if srcField, ok := flgs.SrcNames.TagToFieldName[destTagName]; ok { 849 | srcFieldName = srcField 850 | } 851 | } else { 852 | if srcField, ok := flgs.SrcNames.TagToFieldName[fieldName]; ok { 853 | srcFieldName = srcField 854 | } 855 | } 856 | 857 | if srcFieldName == "" { 858 | srcFieldName = fieldName 859 | } 860 | return 861 | } 862 | 863 | func driverValuer(v reflect.Value) (i driver.Valuer, ok bool) { 864 | if !v.CanAddr() { 865 | i, ok = v.Interface().(driver.Valuer) 866 | return 867 | } 868 | 869 | i, ok = v.Addr().Interface().(driver.Valuer) 870 | return 871 | } 872 | 873 | func fieldByName(v reflect.Value, name string, caseSensitive bool) reflect.Value { 874 | if caseSensitive { 875 | return v.FieldByName(name) 876 | } 877 | 878 | return v.FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, name) }) 879 | } 880 | -------------------------------------------------------------------------------- /copier_test.go: -------------------------------------------------------------------------------- 1 | package copier_test 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | "testing" 9 | "time" 10 | 11 | "github.com/jinzhu/copier" 12 | ) 13 | 14 | type User struct { 15 | Name string 16 | Birthday *time.Time 17 | Nickname string 18 | Role string 19 | Age int32 20 | FakeAge *int32 21 | Notes []string 22 | flags []byte 23 | } 24 | 25 | func (user User) DoubleAge() int32 { 26 | return 2 * user.Age 27 | } 28 | 29 | type Employee struct { 30 | _User *User 31 | Name string 32 | Birthday *time.Time 33 | NickName *string 34 | Age int64 35 | FakeAge int 36 | EmployeID int64 37 | DoubleAge int32 38 | SuperRule string 39 | Notes []*string 40 | flags []byte 41 | } 42 | 43 | func (employee *Employee) Role(role string) { 44 | employee.SuperRule = "Super " + role 45 | } 46 | 47 | func checkEmployee(employee Employee, user User, t *testing.T, testCase string) { 48 | if employee.Name != user.Name { 49 | t.Errorf("%v: Name haven't been copied correctly.", testCase) 50 | } 51 | if employee.NickName == nil || *employee.NickName != user.Nickname { 52 | t.Errorf("%v: NickName haven't been copied correctly.", testCase) 53 | } 54 | if employee.Birthday == nil && user.Birthday != nil { 55 | t.Errorf("%v: Birthday haven't been copied correctly.", testCase) 56 | } 57 | if employee.Birthday != nil && user.Birthday == nil { 58 | t.Errorf("%v: Birthday haven't been copied correctly.", testCase) 59 | } 60 | if employee.Birthday != nil && user.Birthday != nil && 61 | !employee.Birthday.Equal(*(user.Birthday)) { 62 | t.Errorf("%v: Birthday haven't been copied correctly.", testCase) 63 | } 64 | if employee.Age != int64(user.Age) { 65 | t.Errorf("%v: Age haven't been copied correctly.", testCase) 66 | } 67 | if user.FakeAge != nil && employee.FakeAge != int(*user.FakeAge) { 68 | t.Errorf("%v: FakeAge haven't been copied correctly.", testCase) 69 | } 70 | if employee.DoubleAge != user.DoubleAge() { 71 | t.Errorf("%v: Copy from method doesn't work", testCase) 72 | } 73 | if employee.SuperRule != "Super "+user.Role { 74 | t.Errorf("%v: Copy to method doesn't work", testCase) 75 | } 76 | 77 | if len(employee.Notes) != len(user.Notes) { 78 | t.Fatalf("%v: Copy from slice doesn't work, employee notes len: %v, user: %v", testCase, len(employee.Notes), len(user.Notes)) 79 | } 80 | 81 | for idx, note := range user.Notes { 82 | if note != *employee.Notes[idx] { 83 | t.Fatalf("%v: Copy from slice doesn't work, notes idx: %v employee: %v user: %v", testCase, idx, *employee.Notes[idx], note) 84 | } 85 | } 86 | } 87 | 88 | func TestCopySameStructWithPointerField(t *testing.T) { 89 | var fakeAge int32 = 12 90 | var currentTime time.Time = time.Now() 91 | user := &User{Birthday: ¤tTime, Name: "Jinzhu", Nickname: "jinzhu", Age: 18, FakeAge: &fakeAge, Role: "Admin", Notes: []string{"hello world", "welcome"}, flags: []byte{'x'}} 92 | newUser := &User{} 93 | copier.Copy(newUser, user) 94 | if user.Birthday == newUser.Birthday { 95 | t.Errorf("TestCopySameStructWithPointerField: copy Birthday failed since they need to have different address") 96 | } 97 | 98 | if user.FakeAge == newUser.FakeAge { 99 | t.Errorf("TestCopySameStructWithPointerField: copy FakeAge failed since they need to have different address") 100 | } 101 | } 102 | 103 | func checkEmployee2(employee Employee, user *User, t *testing.T, testCase string) { 104 | if user == nil { 105 | if employee.Name != "" || employee.NickName != nil || employee.Birthday != nil || employee.Age != 0 || 106 | employee.DoubleAge != 0 || employee.FakeAge != 0 || employee.SuperRule != "" || employee.Notes != nil { 107 | t.Errorf("%v : employee should be empty", testCase) 108 | } 109 | return 110 | } 111 | 112 | checkEmployee(employee, *user, t, testCase) 113 | } 114 | 115 | func TestCopySliceOfDifferentTypes(t *testing.T) { 116 | var ss []string 117 | var is []int 118 | if err := copier.Copy(&ss, is); err != nil { 119 | t.Error(err) 120 | } 121 | var anotherSs []string 122 | if !reflect.DeepEqual(ss, anotherSs) { 123 | t.Errorf("Copy nil slice to nil slice should get nil slice") 124 | } 125 | } 126 | 127 | func TestCopyStruct(t *testing.T) { 128 | var fakeAge int32 = 12 129 | user := User{Name: "Jinzhu", Nickname: "jinzhu", Age: 18, FakeAge: &fakeAge, Role: "Admin", Notes: []string{"hello world", "welcome"}, flags: []byte{'x'}} 130 | employee := Employee{} 131 | 132 | if err := copier.Copy(employee, &user); err == nil { 133 | t.Errorf("Copy to unaddressable value should get error") 134 | } 135 | 136 | copier.Copy(&employee, &user) 137 | checkEmployee(employee, user, t, "Copy From Ptr To Ptr") 138 | 139 | employee2 := Employee{} 140 | copier.Copy(&employee2, user) 141 | checkEmployee(employee2, user, t, "Copy From Struct To Ptr") 142 | 143 | employee3 := Employee{} 144 | ptrToUser := &user 145 | copier.Copy(&employee3, &ptrToUser) 146 | checkEmployee(employee3, user, t, "Copy From Double Ptr To Ptr") 147 | 148 | employee4 := &Employee{} 149 | copier.Copy(&employee4, user) 150 | checkEmployee(*employee4, user, t, "Copy From Ptr To Double Ptr") 151 | 152 | employee5 := &Employee{} 153 | copier.Copy(&employee5, &employee) 154 | checkEmployee(*employee5, user, t, "Copy From Employee To Employee") 155 | } 156 | 157 | func TestCopyFromStructToSlice(t *testing.T) { 158 | user := User{Name: "Jinzhu", Age: 18, Role: "Admin", Notes: []string{"hello world"}} 159 | employees := []Employee{} 160 | 161 | if err := copier.Copy(employees, &user); err != nil && len(employees) != 0 { 162 | t.Errorf("Copy to unaddressable value should get error") 163 | } 164 | 165 | if copier.Copy(&employees, &user); len(employees) != 1 { 166 | t.Errorf("Should only have one elem when copy struct to slice") 167 | } else { 168 | checkEmployee(employees[0], user, t, "Copy From Struct To Slice Ptr") 169 | } 170 | 171 | employees2 := &[]Employee{} 172 | if copier.Copy(&employees2, user); len(*employees2) != 1 { 173 | t.Errorf("Should only have one elem when copy struct to slice") 174 | } else { 175 | checkEmployee((*employees2)[0], user, t, "Copy From Struct To Double Slice Ptr") 176 | } 177 | 178 | employees3 := []*Employee{} 179 | if copier.Copy(&employees3, user); len(employees3) != 1 { 180 | t.Errorf("Should only have one elem when copy struct to slice") 181 | } else { 182 | checkEmployee(*(employees3[0]), user, t, "Copy From Struct To Ptr Slice Ptr") 183 | } 184 | 185 | employees4 := &[]*Employee{} 186 | if copier.Copy(&employees4, user); len(*employees4) != 1 { 187 | t.Errorf("Should only have one elem when copy struct to slice") 188 | } else { 189 | checkEmployee(*((*employees4)[0]), user, t, "Copy From Struct To Double Ptr Slice Ptr") 190 | } 191 | } 192 | 193 | func TestCopyFromSliceToSlice(t *testing.T) { 194 | users := []User{ 195 | {Name: "Jinzhu", Age: 18, Role: "Admin", Notes: []string{"hello world"}}, 196 | {Name: "Jinzhu2", Age: 22, Role: "Dev", Notes: []string{"hello world", "hello"}}} 197 | employees := []Employee{} 198 | 199 | if copier.Copy(&employees, users); len(employees) != 2 { 200 | t.Errorf("Should have two elems when copy slice to slice") 201 | } else { 202 | checkEmployee(employees[0], users[0], t, "Copy From Slice To Slice Ptr @ 1") 203 | checkEmployee(employees[1], users[1], t, "Copy From Slice To Slice Ptr @ 2") 204 | } 205 | 206 | employees2 := &[]Employee{} 207 | if copier.Copy(&employees2, &users); len(*employees2) != 2 { 208 | t.Errorf("Should have two elems when copy slice to slice") 209 | } else { 210 | checkEmployee((*employees2)[0], users[0], t, "Copy From Slice Ptr To Double Slice Ptr @ 1") 211 | checkEmployee((*employees2)[1], users[1], t, "Copy From Slice Ptr To Double Slice Ptr @ 2") 212 | } 213 | 214 | employees3 := []*Employee{} 215 | if copier.Copy(&employees3, users); len(employees3) != 2 { 216 | t.Errorf("Should have two elems when copy slice to slice") 217 | } else { 218 | checkEmployee(*(employees3[0]), users[0], t, "Copy From Slice To Ptr Slice Ptr @ 1") 219 | checkEmployee(*(employees3[1]), users[1], t, "Copy From Slice To Ptr Slice Ptr @ 2") 220 | } 221 | 222 | employees4 := &[]*Employee{} 223 | if copier.Copy(&employees4, users); len(*employees4) != 2 { 224 | t.Errorf("Should have two elems when copy slice to slice") 225 | } else { 226 | checkEmployee(*((*employees4)[0]), users[0], t, "Copy From Slice Ptr To Double Ptr Slice Ptr @ 1") 227 | checkEmployee(*((*employees4)[1]), users[1], t, "Copy From Slice Ptr To Double Ptr Slice Ptr @ 2") 228 | } 229 | } 230 | 231 | func TestCopyFromSliceToSlice2(t *testing.T) { 232 | users := []*User{{Name: "Jinzhu", Age: 18, Role: "Admin", Notes: []string{"hello world"}}, nil} 233 | employees := []Employee{} 234 | 235 | if copier.Copy(&employees, users); len(employees) != 2 { 236 | t.Errorf("Should have two elems when copy slice to slice") 237 | } else { 238 | checkEmployee2(employees[0], users[0], t, "Copy From Slice To Slice Ptr @ 1") 239 | checkEmployee2(employees[1], users[1], t, "Copy From Slice To Slice Ptr @ 2") 240 | } 241 | 242 | employees2 := &[]Employee{} 243 | if copier.Copy(&employees2, &users); len(*employees2) != 2 { 244 | t.Errorf("Should have two elems when copy slice to slice") 245 | } else { 246 | checkEmployee2((*employees2)[0], users[0], t, "Copy From Slice Ptr To Double Slice Ptr @ 1") 247 | checkEmployee2((*employees2)[1], users[1], t, "Copy From Slice Ptr To Double Slice Ptr @ 2") 248 | } 249 | 250 | employees3 := []*Employee{} 251 | if copier.Copy(&employees3, users); len(employees3) != 2 { 252 | t.Errorf("Should have two elems when copy slice to slice") 253 | } else { 254 | checkEmployee2(*(employees3[0]), users[0], t, "Copy From Slice To Ptr Slice Ptr @ 1") 255 | checkEmployee2(*(employees3[1]), users[1], t, "Copy From Slice To Ptr Slice Ptr @ 2") 256 | } 257 | 258 | employees4 := &[]*Employee{} 259 | if copier.Copy(&employees4, users); len(*employees4) != 2 { 260 | t.Errorf("Should have two elems when copy slice to slice") 261 | } else { 262 | checkEmployee2(*((*employees4)[0]), users[0], t, "Copy From Slice Ptr To Double Ptr Slice Ptr @ 1") 263 | checkEmployee2(*((*employees4)[1]), users[1], t, "Copy From Slice Ptr To Double Ptr Slice Ptr @ 2") 264 | } 265 | } 266 | 267 | func TestCopyFromSliceToSlice3(t *testing.T) { 268 | type CollectionAlias struct { 269 | CollectionName string `json:"collection_name"` 270 | Name string `json:"name"` 271 | } 272 | 273 | expectedResult := []*CollectionAlias{ 274 | {"collection", "collection_alias1"}, 275 | {"collection", "collection_alias2"}, 276 | {"collection", "collection_alias3"}, 277 | } 278 | 279 | mockedResult := []*CollectionAlias{} 280 | copier.Copy(&mockedResult, &expectedResult) 281 | 282 | if len(mockedResult) != len(expectedResult) { 283 | t.Fatalf("failed to copy results") 284 | } 285 | 286 | for idx := range mockedResult { 287 | if mockedResult[idx].Name != mockedResult[idx].Name || mockedResult[idx].CollectionName != mockedResult[idx].CollectionName { 288 | t.Fatalf("failed to copy results") 289 | } 290 | } 291 | } 292 | 293 | func TestEmbeddedAndBase(t *testing.T) { 294 | type Base struct { 295 | BaseField1 int 296 | BaseField2 int 297 | User *User 298 | } 299 | 300 | type Embed struct { 301 | EmbedField1 int 302 | EmbedField2 int 303 | Base 304 | } 305 | 306 | base := Base{} 307 | embedded := Embed{} 308 | embedded.BaseField1 = 1 309 | embedded.BaseField2 = 2 310 | embedded.EmbedField1 = 3 311 | embedded.EmbedField2 = 4 312 | 313 | user := User{ 314 | Name: "testName", 315 | } 316 | embedded.User = &user 317 | 318 | copier.Copy(&base, &embedded) 319 | 320 | if base.BaseField1 != 1 || base.User.Name != "testName" { 321 | t.Error("Embedded fields not copied") 322 | } 323 | 324 | base.BaseField1 = 11 325 | base.BaseField2 = 12 326 | user1 := User{ 327 | Name: "testName1", 328 | } 329 | base.User = &user1 330 | 331 | copier.Copy(&embedded, &base) 332 | if embedded.BaseField1 != 11 || embedded.User.Name != "testName1" { 333 | t.Error("base fields not copied") 334 | } 335 | } 336 | 337 | func TestStructField(t *testing.T) { 338 | type Detail struct { 339 | Info1 string 340 | Info2 *string 341 | } 342 | 343 | type SimilarDetail struct { 344 | Info1 string 345 | Info2 *string 346 | } 347 | 348 | type UserWithDetailsPtr struct { 349 | Details []*Detail 350 | Detail *Detail 351 | Notes *[]string 352 | Notes2 *[]string 353 | } 354 | type UserWithDetails struct { 355 | Details []Detail 356 | Detail Detail 357 | Notes []string 358 | Notes2 []string 359 | } 360 | type UserWithSimilarDetailsPtr struct { 361 | Detail *SimilarDetail 362 | } 363 | type UserWithSimilarDetails struct { 364 | Detail SimilarDetail 365 | } 366 | type EmployeeWithDetails struct { 367 | Detail Detail 368 | } 369 | type EmployeeWithDetailsPtr struct { 370 | Detail *Detail 371 | } 372 | type EmployeeWithSimilarDetails struct { 373 | Detail SimilarDetail 374 | } 375 | type EmployeeWithSimilarDetailsPtr struct { 376 | Detail *SimilarDetail 377 | } 378 | 379 | optionsDeepCopy := copier.Option{ 380 | DeepCopy: true, 381 | } 382 | 383 | checkDetail := func(t *testing.T, source Detail, target Detail) { 384 | if source.Info1 != target.Info1 { 385 | t.Errorf("info1 is diff: source: %v, target: %v", source.Info1, target.Info1) 386 | } 387 | 388 | if (source.Info2 != nil || target.Info2 != nil) && (*source.Info2 != *target.Info2) { 389 | t.Errorf("info2 is diff: source: %v, target: %v", *source.Info2, *target.Info2) 390 | } 391 | } 392 | 393 | t.Run("Should work without deepCopy", func(t *testing.T) { 394 | t.Run("Should work with same type and both ptr field", func(t *testing.T) { 395 | info2 := "world" 396 | from := UserWithDetailsPtr{ 397 | Detail: &Detail{Info1: "hello", Info2: &info2}, 398 | Details: []*Detail{{Info1: "hello", Info2: &info2}}, 399 | } 400 | to := UserWithDetailsPtr{} 401 | copier.Copy(&to, from) 402 | 403 | checkDetail(t, *from.Detail, *to.Detail) 404 | 405 | *to.Detail.Info2 = "new value" 406 | if *from.Detail.Info2 != *to.Detail.Info2 { 407 | t.Fatalf("DeepCopy not enabled") 408 | } 409 | 410 | if len(from.Details) != len(to.Details) { 411 | t.Fatalf("slice should be copied") 412 | } 413 | 414 | for idx, detail := range from.Details { 415 | checkDetail(t, *detail, *to.Details[idx]) 416 | } 417 | }) 418 | 419 | t.Run("Should work with same type and both not ptr field", func(t *testing.T) { 420 | info2 := "world" 421 | from := UserWithDetails{ 422 | Detail: Detail{Info1: "hello", Info2: &info2}, 423 | Details: []Detail{{Info1: "hello", Info2: &info2}}, 424 | } 425 | to := UserWithDetails{} 426 | copier.Copy(&to, from) 427 | 428 | checkDetail(t, from.Detail, to.Detail) 429 | 430 | *to.Detail.Info2 = "new value" 431 | if *from.Detail.Info2 != *to.Detail.Info2 { 432 | t.Fatalf("DeepCopy not enabled") 433 | } 434 | 435 | if len(from.Details) != len(to.Details) { 436 | t.Fatalf("slice should be copied") 437 | } 438 | 439 | for idx, detail := range from.Details { 440 | checkDetail(t, detail, to.Details[idx]) 441 | } 442 | }) 443 | 444 | t.Run("Should work with different type and both ptr field", func(t *testing.T) { 445 | info2 := "world" 446 | from := UserWithDetailsPtr{Detail: &Detail{Info1: "hello", Info2: &info2}} 447 | to := EmployeeWithDetailsPtr{} 448 | copier.Copy(&to, from) 449 | 450 | newValue := "new value" 451 | to.Detail.Info2 = &newValue 452 | 453 | if to.Detail.Info1 == "" { 454 | t.Errorf("should not be empty") 455 | } 456 | if to.Detail.Info1 != from.Detail.Info1 { 457 | t.Errorf("should be the same") 458 | } 459 | if to.Detail.Info2 == from.Detail.Info2 { 460 | t.Errorf("should be different") 461 | } 462 | }) 463 | 464 | t.Run("Should work with different type and both not ptr field", func(t *testing.T) { 465 | info2 := "world" 466 | from := UserWithDetails{Detail: Detail{Info1: "hello", Info2: &info2}} 467 | to := EmployeeWithDetails{} 468 | copier.Copy(&to, from) 469 | 470 | newValue := "new value" 471 | to.Detail.Info2 = &newValue 472 | 473 | if to.Detail.Info1 == "" { 474 | t.Errorf("should not be empty") 475 | } 476 | if to.Detail.Info1 != from.Detail.Info1 { 477 | t.Errorf("should be the same") 478 | } 479 | if to.Detail.Info2 == from.Detail.Info2 { 480 | t.Errorf("should be different") 481 | } 482 | }) 483 | 484 | t.Run("Should work with from ptr field and to not ptr field", func(t *testing.T) { 485 | info2 := "world" 486 | from := UserWithDetailsPtr{Detail: &Detail{Info1: "hello", Info2: &info2}} 487 | to := EmployeeWithDetails{} 488 | copier.Copy(&to, from) 489 | 490 | newValue := "new value" 491 | to.Detail.Info2 = &newValue 492 | 493 | if to.Detail.Info1 == "" { 494 | t.Errorf("should not be empty") 495 | } 496 | if to.Detail.Info1 != from.Detail.Info1 { 497 | t.Errorf("should be the same") 498 | } 499 | if to.Detail.Info2 == from.Detail.Info2 { 500 | t.Errorf("should be different") 501 | } 502 | }) 503 | 504 | t.Run("Should work with from not ptr field and to ptr field", func(t *testing.T) { 505 | info2 := "world" 506 | from := UserWithDetails{Detail: Detail{Info1: "hello", Info2: &info2}} 507 | to := EmployeeWithDetailsPtr{} 508 | copier.Copy(&to, from) 509 | 510 | newValue := "new value" 511 | to.Detail.Info2 = &newValue 512 | 513 | if to.Detail.Info1 == "" { 514 | t.Errorf("should not be empty") 515 | } 516 | if to.Detail.Info1 != from.Detail.Info1 { 517 | t.Errorf("should be the same") 518 | } 519 | if to.Detail.Info2 == from.Detail.Info2 { 520 | t.Errorf("should be different") 521 | } 522 | }) 523 | 524 | t.Run("Should work with from a nil ptr slice field to a slice field", func(t *testing.T) { 525 | notes := []string{"hello", "world"} 526 | from := UserWithDetailsPtr{Notes: ¬es, Notes2: nil} 527 | to := UserWithDetails{} 528 | err := copier.Copy(&to, from) 529 | if err != nil { 530 | t.Errorf("should not return an error") 531 | return 532 | } 533 | 534 | if len(to.Notes) != len(*from.Notes) { 535 | t.Errorf("should be the same length") 536 | } 537 | if to.Notes[0] != (*from.Notes)[0] { 538 | t.Errorf("should be the same") 539 | } 540 | if to.Notes[1] != (*from.Notes)[1] { 541 | t.Errorf("should be the same") 542 | } 543 | }) 544 | }) 545 | 546 | t.Run("Should work with deepCopy", func(t *testing.T) { 547 | t.Run("Should work with same type and both ptr field", func(t *testing.T) { 548 | info2 := "world" 549 | from := UserWithDetailsPtr{ 550 | Detail: &Detail{Info1: "hello", Info2: &info2}, 551 | Details: []*Detail{{Info1: "hello", Info2: &info2}}, 552 | } 553 | to := UserWithDetailsPtr{} 554 | copier.CopyWithOption(&to, from, optionsDeepCopy) 555 | 556 | checkDetail(t, *from.Detail, *to.Detail) 557 | 558 | *to.Detail.Info2 = "new value" 559 | if *from.Detail.Info2 == *to.Detail.Info2 { 560 | t.Fatalf("DeepCopy enabled") 561 | } 562 | 563 | if len(from.Details) != len(to.Details) { 564 | t.Fatalf("slice should be copied") 565 | } 566 | 567 | for idx, detail := range from.Details { 568 | checkDetail(t, *detail, *to.Details[idx]) 569 | } 570 | }) 571 | t.Run("Should work with same type and both not ptr field", func(t *testing.T) { 572 | info2 := "world" 573 | from := UserWithDetails{ 574 | Detail: Detail{Info1: "hello", Info2: &info2}, 575 | Details: []Detail{{Info1: "hello", Info2: &info2}}, 576 | } 577 | to := UserWithDetails{} 578 | copier.CopyWithOption(&to, from, optionsDeepCopy) 579 | 580 | checkDetail(t, from.Detail, to.Detail) 581 | 582 | *to.Detail.Info2 = "new value" 583 | if *from.Detail.Info2 == *to.Detail.Info2 { 584 | t.Fatalf("DeepCopy enabled") 585 | } 586 | 587 | if len(from.Details) != len(to.Details) { 588 | t.Fatalf("slice should be copied") 589 | } 590 | 591 | for idx, detail := range from.Details { 592 | checkDetail(t, detail, to.Details[idx]) 593 | } 594 | }) 595 | 596 | t.Run("Should work with different type and both ptr field", func(t *testing.T) { 597 | info2 := "world" 598 | from := UserWithDetailsPtr{Detail: &Detail{Info1: "hello", Info2: &info2}} 599 | to := EmployeeWithDetailsPtr{} 600 | copier.CopyWithOption(&to, from, optionsDeepCopy) 601 | 602 | newValue := "new value" 603 | to.Detail.Info2 = &newValue 604 | 605 | if to.Detail.Info1 == "" { 606 | t.Errorf("should not be empty") 607 | } 608 | if to.Detail.Info1 != from.Detail.Info1 { 609 | t.Errorf("should be the same") 610 | } 611 | if to.Detail.Info2 == from.Detail.Info2 { 612 | t.Errorf("should be different") 613 | } 614 | }) 615 | 616 | t.Run("Should work with different type and both not ptr field", func(t *testing.T) { 617 | info2 := "world" 618 | from := UserWithDetails{Detail: Detail{Info1: "hello", Info2: &info2}} 619 | to := EmployeeWithDetails{} 620 | copier.CopyWithOption(&to, from, optionsDeepCopy) 621 | 622 | newValue := "new value" 623 | to.Detail.Info2 = &newValue 624 | 625 | if to.Detail.Info1 == "" { 626 | t.Errorf("should not be empty") 627 | } 628 | if to.Detail.Info1 != from.Detail.Info1 { 629 | t.Errorf("should be the same") 630 | } 631 | if to.Detail.Info2 == from.Detail.Info2 { 632 | t.Errorf("should be different") 633 | } 634 | }) 635 | 636 | t.Run("Should work with from ptr field and to not ptr field", func(t *testing.T) { 637 | info2 := "world" 638 | from := UserWithDetailsPtr{Detail: &Detail{Info1: "hello", Info2: &info2}} 639 | to := EmployeeWithDetails{} 640 | copier.CopyWithOption(&to, from, optionsDeepCopy) 641 | 642 | newValue := "new value" 643 | to.Detail.Info2 = &newValue 644 | 645 | if to.Detail.Info1 == "" { 646 | t.Errorf("should not be empty") 647 | } 648 | if to.Detail.Info1 != from.Detail.Info1 { 649 | t.Errorf("should be the same") 650 | } 651 | if to.Detail.Info2 == from.Detail.Info2 { 652 | t.Errorf("should be different") 653 | } 654 | }) 655 | 656 | t.Run("Should work with from not ptr field and to ptr field", func(t *testing.T) { 657 | info2 := "world" 658 | from := UserWithDetails{Detail: Detail{Info1: "hello", Info2: &info2}} 659 | to := EmployeeWithDetailsPtr{} 660 | copier.CopyWithOption(&to, from, optionsDeepCopy) 661 | 662 | newValue := "new value" 663 | to.Detail.Info2 = &newValue 664 | 665 | if to.Detail.Info1 == "" { 666 | t.Errorf("should not be empty") 667 | } 668 | if to.Detail.Info1 != from.Detail.Info1 { 669 | t.Errorf("should be the same") 670 | } 671 | if to.Detail.Info2 == from.Detail.Info2 { 672 | t.Errorf("should be different") 673 | } 674 | }) 675 | 676 | t.Run("Should work with from a nil ptr slice field to a slice field", func(t *testing.T) { 677 | notes := []string{"hello", "world"} 678 | from := UserWithDetailsPtr{Notes: ¬es, Notes2: nil} 679 | to := UserWithDetails{} 680 | err := copier.CopyWithOption(&to, from, optionsDeepCopy) 681 | if err != nil { 682 | t.Errorf("should not return an error") 683 | return 684 | } 685 | 686 | if len(to.Notes) != len(*from.Notes) { 687 | t.Errorf("should be the same length") 688 | } 689 | if to.Notes[0] != (*from.Notes)[0] { 690 | t.Errorf("should be the same") 691 | } 692 | if to.Notes[1] != (*from.Notes)[1] { 693 | t.Errorf("should be the same") 694 | } 695 | 696 | newValue := []string{"new", "value"} 697 | to.Notes = newValue 698 | 699 | if to.Notes[0] == (*from.Notes)[0] { 700 | t.Errorf("should be different") 701 | } 702 | if to.Notes[1] == (*from.Notes)[1] { 703 | t.Errorf("should be different") 704 | } 705 | }) 706 | }) 707 | } 708 | 709 | func TestMapInterface(t *testing.T) { 710 | type Inner struct { 711 | IntPtr *int 712 | unexportedField string 713 | } 714 | 715 | type Outer struct { 716 | Inner Inner 717 | } 718 | 719 | type DriverOptions struct { 720 | GenOptions map[string]interface{} 721 | } 722 | 723 | t.Run("Should work without deepCopy", func(t *testing.T) { 724 | intVal := 5 725 | outer := Outer{ 726 | Inner: Inner{ 727 | IntPtr: &intVal, 728 | unexportedField: "hello", 729 | }, 730 | } 731 | from := DriverOptions{ 732 | GenOptions: map[string]interface{}{ 733 | "key": outer, 734 | }, 735 | } 736 | to := DriverOptions{} 737 | if err := copier.Copy(&to, &from); nil != err { 738 | t.Errorf("Unexpected error: %v", err) 739 | return 740 | } 741 | 742 | *to.GenOptions["key"].(Outer).Inner.IntPtr = 6 743 | 744 | if to.GenOptions["key"].(Outer).Inner.IntPtr != from.GenOptions["key"].(Outer).Inner.IntPtr { 745 | t.Errorf("should be the same") 746 | } 747 | }) 748 | 749 | t.Run("Should work with deepCopy", func(t *testing.T) { 750 | intVal := 5 751 | outer := Outer{ 752 | Inner: Inner{ 753 | IntPtr: &intVal, 754 | unexportedField: "Hello", 755 | }, 756 | } 757 | from := DriverOptions{ 758 | GenOptions: map[string]interface{}{ 759 | "key": outer, 760 | }, 761 | } 762 | to := DriverOptions{} 763 | if err := copier.CopyWithOption(&to, &from, copier.Option{ 764 | DeepCopy: true, 765 | }); nil != err { 766 | t.Errorf("Unexpected error: %v", err) 767 | return 768 | } 769 | 770 | *to.GenOptions["key"].(Outer).Inner.IntPtr = 6 771 | 772 | if to.GenOptions["key"].(Outer).Inner.IntPtr == from.GenOptions["key"].(Outer).Inner.IntPtr { 773 | t.Errorf("should be different") 774 | } 775 | }) 776 | 777 | t.Run("Test copy map with nil interface", func(t *testing.T) { 778 | from := map[string]interface{}{"eventId": nil} 779 | to := map[string]interface{}{"eventId": nil} 780 | copier.CopyWithOption(&to, &from, copier.Option{IgnoreEmpty: true, DeepCopy: true}) 781 | if v, ok := to["eventId"]; !ok || v != nil { 782 | t.Errorf("failed to deep copy map with nil, got %v", v) 783 | } 784 | 785 | from["eventId"] = 1 786 | if v, ok := to["eventId"]; !ok || v != nil { 787 | t.Errorf("failed to deep copy map with nil, got %v", v) 788 | } 789 | 790 | copier.CopyWithOption(&to, &from, copier.Option{IgnoreEmpty: true, DeepCopy: true}) 791 | if v, ok := to["eventId"]; !ok || v != 1 { 792 | t.Errorf("failed to deep copy map with nil") 793 | } 794 | 795 | from["eventId"] = 2 796 | if v, ok := to["eventId"]; !ok || v != 1 { 797 | t.Errorf("failed to deep copy map with nil") 798 | } 799 | }) 800 | 801 | t.Run("Test copy map with nested slice map", func(t *testing.T) { 802 | var out map[string]interface{} 803 | value := map[string]interface{}{ 804 | "list": []map[string]interface{}{ 805 | { 806 | "shop_id": 123, 807 | }, 808 | }, 809 | "list2": []interface{}{ 810 | map[string]interface{}{ 811 | "shop_id": 123, 812 | }, 813 | }, 814 | } 815 | err := copier.CopyWithOption(&out, &value, copier.Option{IgnoreEmpty: false, DeepCopy: true}) 816 | if err != nil { 817 | t.Fatalf("failed to deep copy nested map") 818 | } 819 | if fmt.Sprintf("%v", out) != fmt.Sprintf("%v", value) { 820 | t.Fatalf("failed to deep copy nested map") 821 | } 822 | }) 823 | } 824 | 825 | func TestInterface(t *testing.T) { 826 | type Inner struct { 827 | IntPtr *int 828 | } 829 | 830 | type Outer struct { 831 | Inner Inner 832 | } 833 | 834 | type DriverOptions struct { 835 | GenOptions interface{} 836 | } 837 | 838 | t.Run("Should work without deepCopy", func(t *testing.T) { 839 | intVal := 5 840 | outer := Outer{ 841 | Inner: Inner{ 842 | IntPtr: &intVal, 843 | }, 844 | } 845 | from := DriverOptions{ 846 | GenOptions: outer, 847 | } 848 | to := DriverOptions{} 849 | if err := copier.Copy(&to, from); nil != err { 850 | t.Errorf("Unexpected error: %v", err) 851 | return 852 | } 853 | 854 | *to.GenOptions.(Outer).Inner.IntPtr = 6 855 | 856 | if to.GenOptions.(Outer).Inner.IntPtr != from.GenOptions.(Outer).Inner.IntPtr { 857 | t.Errorf("should be the same") 858 | } 859 | }) 860 | 861 | t.Run("Should work with deepCopy", func(t *testing.T) { 862 | intVal := 5 863 | outer := Outer{ 864 | Inner: Inner{ 865 | IntPtr: &intVal, 866 | }, 867 | } 868 | from := DriverOptions{ 869 | GenOptions: outer, 870 | } 871 | to := DriverOptions{} 872 | if err := copier.CopyWithOption(&to, &from, copier.Option{ 873 | DeepCopy: true, 874 | }); nil != err { 875 | t.Errorf("Unexpected error: %v", err) 876 | return 877 | } 878 | 879 | *to.GenOptions.(Outer).Inner.IntPtr = 6 880 | 881 | if to.GenOptions.(Outer).Inner.IntPtr == from.GenOptions.(Outer).Inner.IntPtr { 882 | t.Errorf("should be different") 883 | } 884 | }) 885 | } 886 | 887 | func TestSlice(t *testing.T) { 888 | type ElemOption struct { 889 | Value int 890 | } 891 | 892 | type A struct { 893 | X []int 894 | Options []ElemOption 895 | } 896 | 897 | type B struct { 898 | X []int 899 | Options []ElemOption 900 | } 901 | 902 | t.Run("Should work with simple slice", func(t *testing.T) { 903 | from := []int{1, 2} 904 | var to []int 905 | 906 | if err := copier.Copy(&to, from); nil != err { 907 | t.Errorf("Unexpected error: %v", err) 908 | return 909 | } 910 | 911 | from[0] = 3 912 | from[1] = 4 913 | 914 | if to[0] == from[0] { 915 | t.Errorf("should be different") 916 | } 917 | 918 | if len(to) != len(from) { 919 | t.Errorf("should be the same length, got len(from): %v, len(to): %v", len(from), len(to)) 920 | } 921 | }) 922 | 923 | t.Run("Should work with empty slice", func(t *testing.T) { 924 | from := []int{} 925 | to := []int{} 926 | 927 | if err := copier.Copy(&to, from); nil != err { 928 | t.Errorf("Unexpected error: %v", err) 929 | return 930 | } 931 | 932 | if to == nil { 933 | t.Errorf("should be not nil") 934 | } 935 | }) 936 | 937 | t.Run("Should work without deepCopy", func(t *testing.T) { 938 | x := []int{1, 2} 939 | options := []ElemOption{ 940 | {Value: 10}, 941 | {Value: 20}, 942 | } 943 | from := A{X: x, Options: options} 944 | to := B{} 945 | 946 | if err := copier.Copy(&to, from); nil != err { 947 | t.Errorf("Unexpected error: %v", err) 948 | return 949 | } 950 | 951 | from.X[0] = 3 952 | from.X[1] = 4 953 | from.Options[0].Value = 30 954 | from.Options[1].Value = 40 955 | 956 | if to.X[0] != from.X[0] { 957 | t.Errorf("should be the same") 958 | } 959 | 960 | if len(to.X) != len(from.X) { 961 | t.Errorf("should be the same length, got len(from.X): %v, len(to.X): %v", len(from.X), len(to.X)) 962 | } 963 | 964 | if to.Options[0].Value != from.Options[0].Value { 965 | t.Errorf("should be the same") 966 | } 967 | 968 | if to.Options[0].Value != from.Options[0].Value { 969 | t.Errorf("should be the same") 970 | } 971 | 972 | if len(to.Options) != len(from.Options) { 973 | t.Errorf("should be the same") 974 | } 975 | }) 976 | 977 | t.Run("Should work with deepCopy", func(t *testing.T) { 978 | x := []int{1, 2} 979 | options := []ElemOption{ 980 | {Value: 10}, 981 | {Value: 20}, 982 | } 983 | from := A{X: x, Options: options} 984 | to := B{} 985 | 986 | if err := copier.CopyWithOption(&to, from, copier.Option{ 987 | DeepCopy: true, 988 | }); nil != err { 989 | t.Errorf("Unexpected error: %v", err) 990 | return 991 | } 992 | 993 | from.X[0] = 3 994 | from.X[1] = 4 995 | from.Options[0].Value = 30 996 | from.Options[1].Value = 40 997 | 998 | if to.X[0] == from.X[0] { 999 | t.Errorf("should be different") 1000 | } 1001 | 1002 | if len(to.X) != len(from.X) { 1003 | t.Errorf("should be the same length, got len(from.X): %v, len(to.X): %v", len(from.X), len(to.X)) 1004 | } 1005 | 1006 | if to.Options[0].Value == from.Options[0].Value { 1007 | t.Errorf("should be different") 1008 | } 1009 | 1010 | if len(to.Options) != len(from.Options) { 1011 | t.Errorf("should be the same") 1012 | } 1013 | }) 1014 | } 1015 | 1016 | func TestAnonymousFields(t *testing.T) { 1017 | t.Run("Should work with unexported ptr fields", func(t *testing.T) { 1018 | type nested struct { 1019 | A string 1020 | } 1021 | type parentA struct { 1022 | *nested 1023 | } 1024 | type parentB struct { 1025 | *nested 1026 | } 1027 | 1028 | from := parentA{nested: &nested{A: "a"}} 1029 | to := parentB{} 1030 | 1031 | err := copier.CopyWithOption(&to, &from, copier.Option{ 1032 | DeepCopy: true, 1033 | }) 1034 | if err != nil { 1035 | t.Errorf("Unexpected error: %v", err) 1036 | return 1037 | } 1038 | 1039 | from.nested.A = "b" 1040 | 1041 | if to.nested != nil { 1042 | t.Errorf("should be nil") 1043 | } 1044 | }) 1045 | t.Run("Should work with unexported fields", func(t *testing.T) { 1046 | type nested struct { 1047 | A string 1048 | } 1049 | type parentA struct { 1050 | nested 1051 | } 1052 | type parentB struct { 1053 | nested 1054 | } 1055 | 1056 | from := parentA{nested: nested{A: "a"}} 1057 | to := parentB{} 1058 | 1059 | err := copier.CopyWithOption(&to, &from, copier.Option{ 1060 | DeepCopy: true, 1061 | }) 1062 | if err != nil { 1063 | t.Errorf("Unexpected error: %v", err) 1064 | return 1065 | } 1066 | 1067 | from.nested.A = "b" 1068 | 1069 | if to.nested.A == from.nested.A { 1070 | t.Errorf("should be different") 1071 | } 1072 | }) 1073 | 1074 | t.Run("Should work with exported ptr fields", func(t *testing.T) { 1075 | type Nested struct { 1076 | A string 1077 | } 1078 | type parentA struct { 1079 | *Nested 1080 | } 1081 | type parentB struct { 1082 | *Nested 1083 | } 1084 | 1085 | fieldValue := "a" 1086 | from := parentA{Nested: &Nested{A: fieldValue}} 1087 | to := parentB{} 1088 | 1089 | err := copier.CopyWithOption(&to, &from, copier.Option{ 1090 | DeepCopy: true, 1091 | }) 1092 | if err != nil { 1093 | t.Errorf("Unexpected error: %v", err) 1094 | return 1095 | } 1096 | 1097 | from.Nested.A = "b" 1098 | 1099 | if to.Nested.A != fieldValue { 1100 | t.Errorf("should not change") 1101 | } 1102 | }) 1103 | 1104 | t.Run("Should work with exported ptr fields with same name src field", func(t *testing.T) { 1105 | type Nested struct { 1106 | A string 1107 | } 1108 | type parentA struct { 1109 | A string 1110 | } 1111 | type parentB struct { 1112 | *Nested 1113 | } 1114 | 1115 | fieldValue := "a" 1116 | from := parentA{A: fieldValue} 1117 | to := parentB{} 1118 | 1119 | err := copier.CopyWithOption(&to, &from, copier.Option{ 1120 | DeepCopy: true, 1121 | }) 1122 | if err != nil { 1123 | t.Errorf("Unexpected error: %v", err) 1124 | return 1125 | } 1126 | 1127 | from.A = "b" 1128 | 1129 | if to.Nested.A != fieldValue { 1130 | t.Errorf("should not change") 1131 | } 1132 | }) 1133 | 1134 | t.Run("Should work with exported fields", func(t *testing.T) { 1135 | type Nested struct { 1136 | A string 1137 | } 1138 | type parentA struct { 1139 | Nested 1140 | } 1141 | type parentB struct { 1142 | Nested 1143 | } 1144 | 1145 | fieldValue := "a" 1146 | from := parentA{Nested: Nested{A: fieldValue}} 1147 | to := parentB{} 1148 | 1149 | err := copier.CopyWithOption(&to, &from, copier.Option{ 1150 | DeepCopy: true, 1151 | }) 1152 | if err != nil { 1153 | t.Errorf("Unexpected error: %v", err) 1154 | return 1155 | } 1156 | 1157 | from.Nested.A = "b" 1158 | 1159 | if to.Nested.A != fieldValue { 1160 | t.Errorf("should not change") 1161 | } 1162 | }) 1163 | } 1164 | 1165 | type someStruct struct { 1166 | IntField int 1167 | UIntField uint64 1168 | } 1169 | 1170 | type structSameName1 struct { 1171 | A string 1172 | B int64 1173 | C time.Time 1174 | D string 1175 | E *someStruct 1176 | } 1177 | 1178 | type structSameName2 struct { 1179 | A string 1180 | B time.Time 1181 | C int64 1182 | D string 1183 | E *someStruct 1184 | } 1185 | 1186 | func TestCopyFieldsWithSameNameButDifferentTypes(t *testing.T) { 1187 | obj1 := structSameName1{A: "123", B: 2, C: time.Now()} 1188 | obj2 := &structSameName2{} 1189 | err := copier.Copy(obj2, &obj1) 1190 | if err != nil { 1191 | t.Error("Should not raise error") 1192 | } 1193 | 1194 | if obj2.A != obj1.A { 1195 | t.Errorf("Field A should be copied") 1196 | } 1197 | } 1198 | 1199 | type Foo1 struct { 1200 | Name string 1201 | Age int32 1202 | } 1203 | 1204 | type Foo2 struct { 1205 | Name string 1206 | } 1207 | 1208 | type StructWithMap1 struct { 1209 | Map map[int]Foo1 1210 | } 1211 | 1212 | type StructWithMap2 struct { 1213 | Map map[int32]Foo2 1214 | } 1215 | 1216 | func TestCopyMapOfStruct(t *testing.T) { 1217 | obj1 := StructWithMap1{Map: map[int]Foo1{2: {Name: "A pure foo"}}} 1218 | obj2 := &StructWithMap2{} 1219 | err := copier.Copy(obj2, obj1) 1220 | if err != nil { 1221 | t.Error("Should not raise error") 1222 | } 1223 | for k, v1 := range obj1.Map { 1224 | v2, ok := obj2.Map[int32(k)] 1225 | if !ok || v1.Name != v2.Name { 1226 | t.Errorf("Map should be copied") 1227 | } 1228 | } 1229 | } 1230 | 1231 | func TestCopyMapOfInt(t *testing.T) { 1232 | map1 := map[int]int{3: 6, 4: 8} 1233 | map2 := map[int32]int8{} 1234 | err := copier.Copy(&map2, map1) 1235 | if err != nil { 1236 | t.Error("Should not raise error") 1237 | } 1238 | 1239 | for k, v1 := range map1 { 1240 | v2, ok := map2[int32(k)] 1241 | if !ok || v1 != int(v2) { 1242 | t.Errorf("Map should be copied") 1243 | } 1244 | } 1245 | } 1246 | 1247 | func TestCopyMapOfSliceValue(t *testing.T) { 1248 | // case1: map's value is a simple slice 1249 | key, value := 2, 3 1250 | src := map[int][]int{key: {value}} 1251 | 1252 | dst1 := map[int][]int{} 1253 | var dst2 map[int][]int 1254 | err := copier.Copy(&dst1, src) 1255 | if err != nil { 1256 | t.Error("Should not raise error") 1257 | } 1258 | err = copier.Copy(&dst2, src) 1259 | if err != nil { 1260 | t.Error("Should not raise error") 1261 | } 1262 | 1263 | for k, v1 := range src { 1264 | v2, ok := dst1[k] 1265 | if !ok || len(v1) != len(v2) || k != key { 1266 | t.Errorf("Map should be copied") 1267 | } 1268 | for i := range v1 { 1269 | if v2[i] != value { 1270 | t.Errorf("Map's slice value shoud be copied") 1271 | } 1272 | } 1273 | 1274 | v3, ok := dst2[k] 1275 | if !ok || len(v1) != len(v3) { 1276 | t.Errorf("Map should be copied") 1277 | } 1278 | for i := range v1 { 1279 | if v3[i] != value { 1280 | t.Errorf("Map's slice value shoud be copied") 1281 | } 1282 | } 1283 | } 1284 | 1285 | // case2: map's value is a slice whose element is map 1286 | key1, key2 := 2, 3 1287 | value = 4 1288 | s := map[int][]map[int]int{key1: {{key2: value}}} 1289 | d1 := map[int][]map[int]int{key1: {{key1: key2}}} 1290 | d2 := map[int][]map[int]int{key1: {}} 1291 | d3 := map[int][]map[int]int{key1: nil} 1292 | d4 := map[int][]map[int]int{} 1293 | d5 := map[int][]map[int]int(nil) 1294 | ms := []map[int][]map[int]int{d1, d2, d3, d4, d5} 1295 | for i := range ms { 1296 | copier.CopyWithOption(&ms[i], s, copier.Option{IgnoreEmpty: false, DeepCopy: true}) 1297 | 1298 | if len(ms[i]) != len(s) { 1299 | t.Errorf("Number of map's keys should be equal") 1300 | } 1301 | for k, sliceMap := range ms[i] { 1302 | if k != key1 { 1303 | t.Errorf("Map's key should be copied") 1304 | } 1305 | if len(sliceMap) != len(s[key1]) || len(sliceMap) != 1 { 1306 | t.Errorf("Map's slice value should be copied") 1307 | } 1308 | m := sliceMap[0] 1309 | if len(m) != len(s[key1][0]) || len(m) != 1 { 1310 | t.Errorf("Map's slice value should be copied recursively") 1311 | } 1312 | for k, v := range m { 1313 | if k != key2 || v != value { 1314 | t.Errorf("Map's slice value should be copied recursively") 1315 | } 1316 | } 1317 | } 1318 | } 1319 | } 1320 | 1321 | func TestCopyMapOfPtrValue(t *testing.T) { 1322 | intV := 3 1323 | intv := intV 1324 | src := map[int]*int{2: &intv} 1325 | dst1 := map[int]*int{} 1326 | var dst2 map[int]*int 1327 | err := copier.Copy(&dst1, src) 1328 | if err != nil { 1329 | t.Error("Should not raise error") 1330 | } 1331 | err = copier.Copy(&dst2, src) 1332 | if err != nil { 1333 | t.Error("Should not raise error") 1334 | } 1335 | 1336 | for k, v1 := range src { 1337 | v2, ok := dst1[k] 1338 | if !ok || v2 == nil || v1 == nil || *v2 != *v1 || *v2 != intV { 1339 | t.Errorf("Map should be copied") 1340 | } 1341 | 1342 | v3, ok := dst2[k] 1343 | if !ok || v3 == nil || *v3 != *v1 || *v3 != intV { 1344 | t.Errorf("Map should be copied") 1345 | } 1346 | } 1347 | } 1348 | 1349 | func TestCopyWithOption(t *testing.T) { 1350 | from := structSameName2{D: "456", E: &someStruct{IntField: 100, UIntField: 1000}} 1351 | to := &structSameName1{A: "123", B: 2, C: time.Now(), D: "123", E: &someStruct{UIntField: 5000}} 1352 | if err := copier.CopyWithOption(to, &from, copier.Option{IgnoreEmpty: true}); err != nil { 1353 | t.Error("Should not raise error") 1354 | } 1355 | 1356 | if to.A == from.A { 1357 | t.Errorf("Field A should not be copied") 1358 | } else if to.D != from.D { 1359 | t.Errorf("Field D should be copied") 1360 | } 1361 | } 1362 | 1363 | type ScannerValue struct { 1364 | V int 1365 | } 1366 | 1367 | func (s *ScannerValue) Scan(src interface{}) error { 1368 | return errors.New("I failed") 1369 | } 1370 | 1371 | type ScannerStruct struct { 1372 | V *ScannerValue 1373 | } 1374 | 1375 | type ScannerStructTo struct { 1376 | V *ScannerValue 1377 | } 1378 | 1379 | func TestScanner(t *testing.T) { 1380 | s := &ScannerStruct{ 1381 | V: &ScannerValue{ 1382 | V: 12, 1383 | }, 1384 | } 1385 | 1386 | s2 := &ScannerStructTo{} 1387 | 1388 | err := copier.Copy(s2, s) 1389 | if err != nil { 1390 | t.Error("Should not raise error") 1391 | } 1392 | 1393 | if s.V.V != s2.V.V { 1394 | t.Errorf("Field V should be copied") 1395 | } 1396 | } 1397 | 1398 | func TestScanFromPtrToSqlNullable(t *testing.T) { 1399 | var ( 1400 | from struct { 1401 | S string 1402 | Sptr *string 1403 | Snull sql.NullString 1404 | T1 sql.NullTime 1405 | T2 sql.NullTime 1406 | T3 *time.Time 1407 | } 1408 | 1409 | to struct { 1410 | S sql.NullString 1411 | Sptr sql.NullString 1412 | Snull *string 1413 | T1 time.Time 1414 | T2 *time.Time 1415 | T3 sql.NullTime 1416 | } 1417 | 1418 | s string 1419 | 1420 | err error 1421 | ) 1422 | 1423 | s = "test" 1424 | from.S = s 1425 | from.Sptr = &s 1426 | 1427 | if from.T1.Valid || from.T2.Valid { 1428 | t.Errorf("Must be not valid") 1429 | } 1430 | 1431 | err = copier.Copy(&to, from) 1432 | if err != nil { 1433 | t.Error("Should not raise error") 1434 | } 1435 | 1436 | if !to.T1.IsZero() { 1437 | t.Errorf("to.T1 should be Zero but %v", to.T1) 1438 | } 1439 | 1440 | if to.T2 != nil { 1441 | t.Errorf("to.T2 should be nil but %v", to.T2) 1442 | } 1443 | 1444 | if to.Snull != nil { 1445 | t.Errorf("to.Snull should be nil but %v", to.Snull) 1446 | } 1447 | 1448 | now := time.Now() 1449 | 1450 | from.T1.Scan(now) 1451 | from.T2.Scan(now) 1452 | 1453 | err = copier.Copy(&to, from) 1454 | if err != nil { 1455 | t.Error("Should not raise error") 1456 | } 1457 | 1458 | if to.S.String != from.S { 1459 | t.Errorf("Field S should be copied") 1460 | } 1461 | 1462 | if to.Sptr.String != *from.Sptr { 1463 | t.Errorf("Field Sptr should be copied") 1464 | } 1465 | 1466 | if from.T1.Time != to.T1 { 1467 | t.Errorf("Fields T1 fields should be equal") 1468 | } 1469 | 1470 | if from.T2.Time != *to.T2 { 1471 | t.Errorf("Fields T2 fields should be equal") 1472 | } 1473 | } 1474 | 1475 | func TestDeepCopyInterface(t *testing.T) { 1476 | m := make(map[string]string) 1477 | m["a"] = "ccc" 1478 | 1479 | from := []interface{}{[]int{7, 8, 9}, 2, 3, m, errors.New("aaaa")} 1480 | var to []interface{} 1481 | 1482 | copier.CopyWithOption(&to, &from, copier.Option{ 1483 | IgnoreEmpty: false, 1484 | DeepCopy: true, 1485 | }) 1486 | 1487 | from[0].([]int)[0] = 10 1488 | from[1] = "3" 1489 | from[3].(map[string]string)["a"] = "bbb" 1490 | 1491 | if fmt.Sprint(to[0]) != fmt.Sprint([]int{7, 8, 9}) { 1492 | t.Errorf("to value failed to be deep copied") 1493 | } 1494 | 1495 | if fmt.Sprint(to[1]) != "2" { 1496 | t.Errorf("to value failed to be deep copied") 1497 | } 1498 | 1499 | if to[3].(map[string]string)["a"] != "ccc" { 1500 | t.Errorf("to value failed to be deep copied") 1501 | } 1502 | } 1503 | 1504 | func TestDeepCopyTime(t *testing.T) { 1505 | type embedT1 struct { 1506 | T5 time.Time 1507 | } 1508 | 1509 | type embedT2 struct { 1510 | T6 *time.Time 1511 | } 1512 | 1513 | var ( 1514 | from struct { 1515 | T1 time.Time 1516 | T2 *time.Time 1517 | 1518 | T3 *time.Time 1519 | T4 time.Time 1520 | T5 time.Time 1521 | T6 time.Time 1522 | } 1523 | 1524 | to struct { 1525 | T1 time.Time 1526 | T2 *time.Time 1527 | 1528 | T3 time.Time 1529 | T4 *time.Time 1530 | embedT1 1531 | embedT2 1532 | } 1533 | ) 1534 | 1535 | t1 := time.Now() 1536 | from.T1 = t1 1537 | t2 := t1.Add(time.Second) 1538 | from.T2 = &t2 1539 | t3 := t2.Add(time.Second) 1540 | from.T3 = &t3 1541 | t4 := t3.Add(time.Second) 1542 | from.T4 = t4 1543 | t5 := t4.Add(time.Second) 1544 | from.T5 = t5 1545 | t6 := t5.Add(time.Second) 1546 | from.T6 = t6 1547 | 1548 | err := copier.CopyWithOption(&to, from, copier.Option{DeepCopy: true}) 1549 | if err != nil { 1550 | t.Error("Should not raise error") 1551 | } 1552 | 1553 | if !to.T1.Equal(from.T1) { 1554 | t.Errorf("Field T1 should be copied") 1555 | } 1556 | if !to.T2.Equal(*from.T2) { 1557 | t.Errorf("Field T2 should be copied") 1558 | } 1559 | if !to.T3.Equal(*from.T3) { 1560 | t.Errorf("Field T3 should be copied") 1561 | } 1562 | if !to.T4.Equal(from.T4) { 1563 | t.Errorf("Field T4 should be copied") 1564 | } 1565 | if !to.T5.Equal(from.T5) { 1566 | t.Errorf("Field T5 should be copied") 1567 | } 1568 | if !to.T6.Equal(from.T6) { 1569 | t.Errorf("Field T6 should be copied") 1570 | } 1571 | } 1572 | 1573 | func TestNestedPrivateData(t *testing.T) { 1574 | type hasPrivate struct { 1575 | data int 1576 | } 1577 | 1578 | type hasMembers struct { 1579 | Member hasPrivate 1580 | } 1581 | 1582 | src := hasMembers{ 1583 | Member: hasPrivate{ 1584 | data: 42, 1585 | }, 1586 | } 1587 | var shallow hasMembers 1588 | err := copier.Copy(&shallow, &src) 1589 | if err != nil { 1590 | t.Errorf("could not complete shallow copy") 1591 | } 1592 | if !reflect.DeepEqual(&src, &shallow) { 1593 | t.Errorf("shallow copy faild") 1594 | } 1595 | 1596 | var deep hasMembers 1597 | err = copier.CopyWithOption(&deep, &src, copier.Option{DeepCopy: true}) 1598 | if err != nil { 1599 | t.Errorf("could not complete deep copy") 1600 | } 1601 | if !reflect.DeepEqual(&src, &deep) { 1602 | t.Errorf("deep copy faild") 1603 | } 1604 | 1605 | if !reflect.DeepEqual(&shallow, &deep) { 1606 | t.Errorf("unexpected difference between shallow and deep copy") 1607 | } 1608 | } 1609 | 1610 | func TestDeepMapCopyTime(t *testing.T) { 1611 | t1 := time.Now() 1612 | t2 := t1.Add(time.Second) 1613 | from := []map[string]interface{}{ 1614 | { 1615 | "t1": t1, 1616 | "t2": &t2, 1617 | }, 1618 | } 1619 | to := make([]map[string]interface{}, len(from)) 1620 | 1621 | err := copier.CopyWithOption(&to, from, copier.Option{DeepCopy: true}) 1622 | if err != nil { 1623 | t.Error("should not error") 1624 | } 1625 | if len(to) != len(from) { 1626 | t.Errorf("slice should be copied") 1627 | } 1628 | if !to[0]["t1"].(time.Time).Equal(from[0]["t1"].(time.Time)) { 1629 | t.Errorf("nested time ptr should be copied") 1630 | } 1631 | if !to[0]["t2"].(*time.Time).Equal(*from[0]["t2"].(*time.Time)) { 1632 | t.Errorf("nested time ptr should be copied") 1633 | } 1634 | } 1635 | 1636 | func TestCopySimpleTime(t *testing.T) { 1637 | from := time.Now() 1638 | to := time.Time{} 1639 | 1640 | err := copier.Copy(&to, from) 1641 | if err != nil { 1642 | t.Error("should not error") 1643 | } 1644 | if !from.Equal(to) { 1645 | t.Errorf("to (%v) value should equal from (%v) value", to, from) 1646 | } 1647 | } 1648 | 1649 | func TestDeepCopySimpleTime(t *testing.T) { 1650 | from := time.Now() 1651 | to := time.Time{} 1652 | 1653 | err := copier.CopyWithOption(&to, from, copier.Option{DeepCopy: true}) 1654 | if err != nil { 1655 | t.Error("should not error") 1656 | } 1657 | if !from.Equal(to) { 1658 | t.Errorf("to (%v) value should equal from (%v) value", to, from) 1659 | } 1660 | } 1661 | 1662 | type TimeWrapper struct { 1663 | time.Time 1664 | } 1665 | 1666 | func TestDeepCopyAnonymousFieldTime(t *testing.T) { 1667 | from := TimeWrapper{time.Now()} 1668 | to := TimeWrapper{} 1669 | 1670 | err := copier.CopyWithOption(&to, from, copier.Option{DeepCopy: true}) 1671 | if err != nil { 1672 | t.Error("should not error") 1673 | } 1674 | if !from.Time.Equal(to.Time) { 1675 | t.Errorf("to (%v) value should equal from (%v) value", to.Time, from.Time) 1676 | } 1677 | } 1678 | 1679 | func TestSqlNullFiled(t *testing.T) { 1680 | 1681 | type sqlStruct struct { 1682 | MkId sql.NullInt64 1683 | MkExpiryDateType sql.NullInt32 1684 | MkExpiryDateStart sql.NullString 1685 | } 1686 | 1687 | type dataStruct struct { 1688 | MkId int64 1689 | MkExpiryDateType int32 1690 | MkExpiryDateStart string 1691 | } 1692 | 1693 | from := sqlStruct{ 1694 | MkId: sql.NullInt64{Int64: 3, Valid: true}, 1695 | MkExpiryDateType: sql.NullInt32{Int32: 4, Valid: true}, 1696 | MkExpiryDateStart: sql.NullString{String: "5", Valid: true}, 1697 | } 1698 | 1699 | to := dataStruct{} 1700 | 1701 | err := copier.Copy(&to, from) 1702 | if err != nil { 1703 | t.Error("should not error") 1704 | } 1705 | if from.MkId.Int64 != to.MkId { 1706 | t.Errorf("to (%v) value should equal from (%v) value", to.MkId, from.MkId.Int64) 1707 | } 1708 | 1709 | if from.MkExpiryDateStart.String != to.MkExpiryDateStart { 1710 | t.Errorf("to (%v) value should equal from (%v) value", to.MkExpiryDateStart, from.MkExpiryDateStart.String) 1711 | } 1712 | 1713 | if from.MkExpiryDateType.Int32 != to.MkExpiryDateType { 1714 | t.Errorf("to (%v) value should equal from (%v) value", to.MkExpiryDateType, from.MkExpiryDateType.Int32) 1715 | } 1716 | } 1717 | 1718 | func TestEmptySlice(t *testing.T) { 1719 | type Str1 string 1720 | type Str2 string 1721 | type Input1 struct { 1722 | Val Str1 1723 | } 1724 | type Input2 struct { 1725 | Val Str2 1726 | } 1727 | to := []*Input1(nil) 1728 | from := []*Input2{} 1729 | err := copier.Copy(&to, &from) 1730 | if err != nil { 1731 | t.Error("should not error") 1732 | } 1733 | if from == nil { 1734 | t.Error("from should be empty slice not nil") 1735 | } 1736 | 1737 | to = []*Input1(nil) 1738 | from = []*Input2(nil) 1739 | err = copier.Copy(&to, &from) 1740 | if err != nil { 1741 | t.Error("should not error") 1742 | } 1743 | if from != nil { 1744 | t.Error("from should be empty slice nil") 1745 | } 1746 | } 1747 | 1748 | func TestNestedNilPointerStruct(t *testing.T) { 1749 | type destination struct { 1750 | Title string 1751 | } 1752 | 1753 | type NestedSource struct { 1754 | ID int 1755 | } 1756 | 1757 | type source struct { 1758 | Title string 1759 | *NestedSource 1760 | } 1761 | 1762 | from := &source{ 1763 | Title: "A title to be copied", 1764 | } 1765 | 1766 | to := destination{} 1767 | 1768 | err := copier.Copy(&to, from) 1769 | if err != nil { 1770 | t.Error("should not error") 1771 | } 1772 | 1773 | if from.Title != to.Title { 1774 | t.Errorf("to (%v) value should equal from (%v) value", to.Title, from.Title) 1775 | } 1776 | } 1777 | --------------------------------------------------------------------------------