-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathcopyStruct.go
More file actions
265 lines (253 loc) · 7.29 KB
/
copyStruct.go
File metadata and controls
265 lines (253 loc) · 7.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
/*
* File: copyStruct.go
* Created Date: 2022-01-26 06:15:08
* Author: ysj
* Description: copy struct to struct
*/
package gocopy
import (
"reflect"
"time"
)
var (
defaultTimeLoc = "Asia/Shanghai"
defaultTimeLayout = "2006-01-02 15:04:05"
)
// copyStruct copy struct to struct
func copyStruct(toValue, fromValue reflect.Value, opt *Option) {
fromValue = indirectValue(fromValue)
toValue = indirectValue(toValue)
// type
fromType, _ := indirectType(fromValue.Type())
toType, _ := indirectType(toValue.Type())
// handle every struct field
fromFields := deepFields(fromType)
for i := 0; i < len(fromFields); i++ {
fromField := fromFields[i]
// ignore field to skip copy
if level, ok := opt.ignoreFields[fromField.Name]; ok {
opt.ignoreFields[fromField.Name]++
if level <= opt.IgnoreLevel {
continue
}
}
// from field to field
toFieldName, ok := opt.NameFromTo[fromField.Name]
if !ok {
toFieldName = fromField.Name
}
// whether field is in toType
toField, ok := toType.FieldByName(toFieldName)
if !ok {
continue
}
fromFValue := fromValue.FieldByName(fromField.Name)
fromFieldValue := indirectValue(fromFValue)
toFieldValue := toValue.FieldByName(toField.Name)
fromFieldType, _ := indirectType(fromField.Type)
toFieldType, toFieldIsPtr := indirectType(toField.Type)
// pointer value like *string *int
if toFieldIsPtr && toFieldValue.IsNil() {
toNewValue := reflect.New(toFieldType)
toFieldValue.Set(toNewValue)
}
if !toFieldValue.CanSet() || !fromFieldValue.IsValid() {
continue
}
// avoid tofield is zero slice/map, set on zero value will panic
if toFieldValue.IsZero() {
if toFieldIsPtr {
toFieldValue.Set(reflect.New(toField.Type))
} else {
toFieldValue.Set(indirectValue(reflect.New(toField.Type)))
}
}
// convert field value by customized func
if convertFunc, ok := opt.Converters[fromField.Name]; ok {
convertValue := convertFunc(fromFValue.Interface())
if toFieldIsPtr {
toFV := indirectValue(reflect.New(toFieldType))
toFV.Set(reflect.ValueOf(convertValue))
toFieldValue.Set(toFV.Addr())
} else {
// sometimes convertValue maybe nil
if convertValue == nil {
toFieldValue.Set(reflect.Zero(toFieldValue.Type()))
} else {
toFieldValue.Set(reflect.ValueOf(convertValue))
}
}
continue
}
// specially handle time.Time to string and vice versa
if timeFieldMap, ok := opt.TimeToString[fromField.Name]; ok {
timeString := ""
if timeFieldMap == nil {
location, err := time.LoadLocation(defaultTimeLoc)
if err != nil {
panic(err)
}
timeString = fromFieldValue.Interface().(time.Time).In(location).Format(defaultTimeLayout)
} else {
loc, ok := timeFieldMap["loc"]
if !ok {
loc = defaultTimeLoc
}
layout, ok := timeFieldMap["layout"]
if !ok {
layout = defaultTimeLayout
}
location, err := time.LoadLocation(loc)
if err != nil {
panic(err)
}
timeString = fromFieldValue.Interface().(time.Time).In(location).Format(layout)
}
if toFieldIsPtr {
toFV := indirectValue(reflect.New(toFieldType))
toFV.Set(reflect.ValueOf(timeString))
toFieldValue.Set(toFV.Addr())
} else {
toFieldValue.Set(reflect.ValueOf(timeString))
}
continue
}
if stringFieldMap, ok := opt.StringToTime[fromField.Name]; ok {
if fromFieldValue.IsZero() { // ""
continue
}
var timeTime = time.Now()
if stringFieldMap == nil {
location, err := time.LoadLocation(defaultTimeLoc)
if err != nil {
panic(err)
}
timeTime, err = time.ParseInLocation(defaultTimeLayout, fromFieldValue.Interface().(string), location)
if err != nil {
panic(err)
}
} else {
loc, ok := stringFieldMap["loc"]
if !ok {
loc = defaultTimeLoc
}
layout, ok := stringFieldMap["layout"]
if !ok {
layout = defaultTimeLayout
}
location, err := time.LoadLocation(loc)
if err != nil {
panic(err)
}
timeTime, err = time.ParseInLocation(layout, fromFieldValue.Interface().(string), location)
if err != nil {
panic(err)
}
}
if toFieldIsPtr {
toFV := indirectValue(reflect.New(toFieldType))
toFV.Set(reflect.ValueOf(timeTime))
toFieldValue.Set(toFV.Addr())
} else {
toFieldValue.Set(reflect.ValueOf(timeTime))
}
continue
}
// can direct assign
// 可直接赋值拷贝
if fromFieldType.AssignableTo(toFieldType) {
if !opt.Append { // not append
if toFieldIsPtr {
// like string -> *string
if fromFieldValue.CanAddr() {
toFieldValue.Set(fromFieldValue.Addr())
} else {
fromFV := indirectValue(reflect.New(fromFieldType))
fromFV.Set(fromFieldValue)
toFieldValue.Set(fromFV.Addr())
}
} else {
toFieldValue.Set(fromFieldValue)
}
} else { // append mode
fromFieldKind := fromFieldType.Kind()
switch fromFieldKind {
// slice append slice, need to avoid zero slice
case reflect.Slice:
copySlice(toFieldValue, fromFieldValue, opt)
// map set kv, need to avoid nil map
case reflect.Map:
copyMap(toFieldValue, fromFieldValue, opt)
default:
if toFieldIsPtr {
// like string -> *string
if fromFieldValue.CanAddr() {
toFieldValue.Set(fromFieldValue.Addr())
} else {
fromFV := indirectValue(reflect.New(fromFieldType))
fromFV.Set(fromFieldValue)
toFieldValue.Set(fromFV.Addr())
}
} else {
toFieldValue.Set(fromFieldValue)
}
}
}
// can convert field type
// 类型可转换拷贝
} else if fromFieldType.ConvertibleTo(toFieldType) {
convertValue := fromFieldValue.Convert(toFieldType)
if !opt.Append { // not append
if toFieldIsPtr {
// like string -> *string
if convertValue.CanAddr() {
toFieldValue.Set(convertValue.Addr())
} else {
convertFV := indirectValue(reflect.New(toFieldType))
convertFV.Set(convertValue)
toFieldValue.Set(convertFV.Addr())
}
} else {
toFieldValue.Set(convertValue) // set to converted value
}
} else { // append mode
fromFieldKind := fromFieldType.Kind()
switch fromFieldKind {
// slice append slice, need to avoid zero slice
case reflect.Slice:
copySlice(toFieldValue, convertValue, opt)
// map set kv, need to avoid nil map
case reflect.Map:
copyMap(toFieldValue, convertValue, opt)
default:
if toFieldIsPtr {
// like string -> *string
if convertValue.CanAddr() {
toFieldValue.Set(convertValue.Addr())
} else {
convertFV := indirectValue(reflect.New(toFieldType))
convertFV.Set(convertValue)
toFieldValue.Set(convertFV.Addr())
}
} else {
toFieldValue.Set(convertValue) // set to converted value
}
}
}
} else {
// can not directly assign or convert
fromFieldKind := fromFieldType.Kind()
toFieldKind := toFieldType.Kind()
// 1. slice to slice
if toFieldKind == reflect.Slice && fromFieldKind == reflect.Slice {
copySlice(toFieldValue, fromFieldValue, opt)
// 2. map to map
} else if toFieldKind == reflect.Map && fromFieldKind == reflect.Map {
copyMap(toFieldValue, fromFieldValue, opt)
// 3. struct to struct
} else if toFieldKind == reflect.Struct && fromFieldKind == reflect.Struct {
copyStruct(toFieldValue, fromFieldValue, opt)
}
}
}
}