/
gconv_scan.go
205 lines (188 loc) · 6.57 KB
/
gconv_scan.go
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
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gconv
import (
"reflect"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
)
// Scan automatically checks the type of `pointer` and converts `params` to `pointer`.
// It supports `pointer` in type of `*map/*[]map/*[]*map/*struct/**struct/*[]struct/*[]*struct` for converting.
//
// TODO change `paramKeyToAttrMap` to `ScanOption` to be more scalable; add `DeepCopy` option for `ScanOption`.
func Scan(srcValue interface{}, dstPointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) {
if srcValue == nil {
// If `srcValue` is nil, no conversion.
return nil
}
if dstPointer == nil {
return gerror.NewCode(
gcode.CodeInvalidParameter,
`destination pointer should not be nil`,
)
}
// json converting check.
ok, err := doConvertWithJsonCheck(srcValue, dstPointer)
if err != nil {
return err
}
if ok {
return nil
}
var (
dstPointerReflectType reflect.Type
dstPointerReflectValue reflect.Value
)
if v, ok := dstPointer.(reflect.Value); ok {
dstPointerReflectValue = v
dstPointerReflectType = v.Type()
} else {
dstPointerReflectValue = reflect.ValueOf(dstPointer)
// do not use dstPointerReflectValue.Type() as dstPointerReflectValue might be zero.
dstPointerReflectType = reflect.TypeOf(dstPointer)
}
// pointer kind validation.
var dstPointerReflectKind = dstPointerReflectType.Kind()
if dstPointerReflectKind != reflect.Ptr {
if dstPointerReflectValue.CanAddr() {
dstPointerReflectValue = dstPointerReflectValue.Addr()
dstPointerReflectType = dstPointerReflectValue.Type()
dstPointerReflectKind = dstPointerReflectType.Kind()
} else {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`destination pointer should be type of pointer, but got type: %v`,
dstPointerReflectType,
)
}
}
// direct assignment checks!
var srcValueReflectValue reflect.Value
if v, ok := srcValue.(reflect.Value); ok {
srcValueReflectValue = v
} else {
srcValueReflectValue = reflect.ValueOf(srcValue)
}
// if `srcValue` and `dstPointer` are the same type, the do directly assignment.
// For performance enhancement purpose.
var dstPointerReflectValueElem = dstPointerReflectValue.Elem()
// if `srcValue` and `dstPointer` are the same type, the do directly assignment.
// for performance enhancement purpose.
if ok = doConvertWithTypeCheck(srcValueReflectValue, dstPointerReflectValueElem); ok {
return nil
}
// do the converting.
var (
dstPointerReflectTypeElem = dstPointerReflectType.Elem()
dstPointerReflectTypeElemKind = dstPointerReflectTypeElem.Kind()
keyToAttributeNameMapping map[string]string
)
if len(paramKeyToAttrMap) > 0 {
keyToAttributeNameMapping = paramKeyToAttrMap[0]
}
switch dstPointerReflectTypeElemKind {
case reflect.Map:
return doMapToMap(srcValue, dstPointer, paramKeyToAttrMap...)
case reflect.Array, reflect.Slice:
var (
sliceElem = dstPointerReflectTypeElem.Elem()
sliceElemKind = sliceElem.Kind()
)
for sliceElemKind == reflect.Ptr {
sliceElem = sliceElem.Elem()
sliceElemKind = sliceElem.Kind()
}
if sliceElemKind == reflect.Map {
return doMapToMaps(srcValue, dstPointer, paramKeyToAttrMap...)
}
return doStructs(srcValue, dstPointer, keyToAttributeNameMapping, "")
default:
return doStruct(srcValue, dstPointer, keyToAttributeNameMapping, "")
}
}
func doConvertWithTypeCheck(srcValueReflectValue, dstPointerReflectValueElem reflect.Value) (ok bool) {
if !dstPointerReflectValueElem.IsValid() || !srcValueReflectValue.IsValid() {
return false
}
switch {
// Example:
// UploadFile => UploadFile
// []UploadFile => []UploadFile
// *UploadFile => *UploadFile
// *[]UploadFile => *[]UploadFile
// map => map
// []map => []map
// *[]map => *[]map
case dstPointerReflectValueElem.Type() == srcValueReflectValue.Type():
dstPointerReflectValueElem.Set(srcValueReflectValue)
return true
// Example:
// UploadFile => *UploadFile
// []UploadFile => *[]UploadFile
// map => *map
// []map => *[]map
case dstPointerReflectValueElem.Kind() == reflect.Ptr &&
dstPointerReflectValueElem.Elem().IsValid() &&
dstPointerReflectValueElem.Elem().Type() == srcValueReflectValue.Type():
dstPointerReflectValueElem.Elem().Set(srcValueReflectValue)
return true
// Example:
// *UploadFile => UploadFile
// *[]UploadFile => []UploadFile
// *map => map
// *[]map => []map
case srcValueReflectValue.Kind() == reflect.Ptr &&
srcValueReflectValue.Elem().IsValid() &&
dstPointerReflectValueElem.Type() == srcValueReflectValue.Elem().Type():
dstPointerReflectValueElem.Set(srcValueReflectValue.Elem())
return true
default:
return false
}
}
// doConvertWithJsonCheck does json converting check.
// If given `params` is JSON, it then uses json.Unmarshal doing the converting.
func doConvertWithJsonCheck(srcValue interface{}, dstPointer interface{}) (ok bool, err error) {
switch valueResult := srcValue.(type) {
case []byte:
if json.Valid(valueResult) {
if dstPointerReflectType, ok := dstPointer.(reflect.Value); ok {
if dstPointerReflectType.Kind() == reflect.Ptr {
if dstPointerReflectType.IsNil() {
return false, nil
}
return true, json.UnmarshalUseNumber(valueResult, dstPointerReflectType.Interface())
} else if dstPointerReflectType.CanAddr() {
return true, json.UnmarshalUseNumber(valueResult, dstPointerReflectType.Addr().Interface())
}
} else {
return true, json.UnmarshalUseNumber(valueResult, dstPointer)
}
}
case string:
if valueBytes := []byte(valueResult); json.Valid(valueBytes) {
if dstPointerReflectType, ok := dstPointer.(reflect.Value); ok {
if dstPointerReflectType.Kind() == reflect.Ptr {
if dstPointerReflectType.IsNil() {
return false, nil
}
return true, json.UnmarshalUseNumber(valueBytes, dstPointerReflectType.Interface())
} else if dstPointerReflectType.CanAddr() {
return true, json.UnmarshalUseNumber(valueBytes, dstPointerReflectType.Addr().Interface())
}
} else {
return true, json.UnmarshalUseNumber(valueBytes, dstPointer)
}
}
default:
// The `params` might be struct that implements interface function Interface, eg: gvar.Var.
if v, ok := srcValue.(iInterface); ok {
return doConvertWithJsonCheck(v.Interface(), dstPointer)
}
}
return false, nil
}