github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/mapstruct/hooks.go (about) 1 package mapstruct 2 3 import ( 4 "encoding" 5 "errors" 6 "fmt" 7 "net" 8 "reflect" 9 "strconv" 10 "strings" 11 "time" 12 ) 13 14 // typedDecodeHook takes a raw HookFunc (an interface{}) and turns 15 // it into the proper HookFunc type, such as HookFuncType. 16 func typedDecodeHook(h HookFunc) HookFunc { 17 // Create variables here so we can reference them with the reflect pkg 18 var f1 HookFuncType 19 var f2 HookFuncKind 20 var f3 HookFuncValue 21 22 // Fill in the variables into this interface and the rest is done 23 // automatically using the reflect package. 24 potential := []interface{}{f1, f2, f3} 25 26 v := reflect.ValueOf(h) 27 vt := v.Type() 28 for _, raw := range potential { 29 pt := reflect.ValueOf(raw).Type() 30 if vt.ConvertibleTo(pt) { 31 return v.Convert(pt).Interface() 32 } 33 } 34 35 return nil 36 } 37 38 // DecodeHookExec executes the given decode hook. This should be used 39 // since it'll naturally degrade to the older backwards compatible HookFunc 40 // that took reflect.Kind instead of reflect.Type. 41 func DecodeHookExec(raw HookFunc, from, to reflect.Value) (interface{}, error) { 42 switch f := typedDecodeHook(raw).(type) { 43 case HookFuncType: 44 return f(from.Type(), to.Type(), from.Interface()) 45 case HookFuncKind: 46 return f(from.Kind(), to.Kind(), from.Interface()) 47 case HookFuncValue: 48 return f(from, to) 49 default: 50 return nil, errors.New("invalid decode hook signature") 51 } 52 } 53 54 // ComposeDecodeHookFunc creates a single HookFunc that 55 // automatically composes multiple DecodeHookFuncs. 56 // 57 // The composed funcs are called in order, with the result of the 58 // previous transformation. 59 func ComposeDecodeHookFunc(fs ...HookFunc) HookFunc { 60 return func(f reflect.Value, t reflect.Value) (interface{}, error) { 61 var err error 62 var data interface{} 63 newFrom := f 64 for _, f1 := range fs { 65 data, err = DecodeHookExec(f1, newFrom, t) 66 if err != nil { 67 return nil, err 68 } 69 newFrom = reflect.ValueOf(data) 70 } 71 72 return data, nil 73 } 74 } 75 76 // StringToSliceHookFunc returns a HookFunc that converts 77 // string to []string by splitting on the given sep. 78 func StringToSliceHookFunc(sep string) HookFunc { 79 return func( 80 f reflect.Kind, 81 t reflect.Kind, 82 data interface{}, 83 ) (interface{}, error) { 84 if f != reflect.String || t != reflect.Slice { 85 return data, nil 86 } 87 88 raw := data.(string) 89 if raw == "" { 90 return []string{}, nil 91 } 92 93 return strings.Split(raw, sep), nil 94 } 95 } 96 97 // StringToTimeDurationHookFunc returns a HookFunc that converts 98 // strings to time.Duration. 99 func StringToTimeDurationHookFunc() HookFunc { 100 return func( 101 f reflect.Type, 102 t reflect.Type, 103 data interface{}, 104 ) (interface{}, error) { 105 if f.Kind() != reflect.String { 106 return data, nil 107 } 108 if t != reflect.TypeOf(time.Duration(5)) { 109 return data, nil 110 } 111 112 // Convert it by parsing 113 return time.ParseDuration(data.(string)) 114 } 115 } 116 117 // StringToIPHookFunc returns a HookFunc that converts 118 // strings to net.IP 119 func StringToIPHookFunc() HookFunc { 120 return func( 121 f reflect.Type, 122 t reflect.Type, 123 data interface{}, 124 ) (interface{}, error) { 125 if f.Kind() != reflect.String { 126 return data, nil 127 } 128 if t != reflect.TypeOf(net.IP{}) { 129 return data, nil 130 } 131 132 // Convert it by parsing 133 ip := net.ParseIP(data.(string)) 134 if ip == nil { 135 return net.IP{}, fmt.Errorf("failed parsing ip %v", data) 136 } 137 138 return ip, nil 139 } 140 } 141 142 // StringToIPNetHookFunc returns a HookFunc that converts 143 // strings to net.IPNet 144 func StringToIPNetHookFunc() HookFunc { 145 return func( 146 f reflect.Type, 147 t reflect.Type, 148 data interface{}, 149 ) (interface{}, error) { 150 if f.Kind() != reflect.String { 151 return data, nil 152 } 153 if t != reflect.TypeOf(net.IPNet{}) { 154 return data, nil 155 } 156 157 // Convert it by parsing 158 _, n, err := net.ParseCIDR(data.(string)) 159 return n, err 160 } 161 } 162 163 // StringToTimeHookFunc returns a HookFunc that converts 164 // strings to time.Time. 165 func StringToTimeHookFunc(layout string) HookFunc { 166 return func( 167 f reflect.Type, 168 t reflect.Type, 169 data interface{}, 170 ) (interface{}, error) { 171 if f.Kind() != reflect.String { 172 return data, nil 173 } 174 if t != reflect.TypeOf(time.Time{}) { 175 return data, nil 176 } 177 178 // Convert it by parsing 179 return time.Parse(layout, data.(string)) 180 } 181 } 182 183 // WeaklyTypedHook is a HookFunc which adds support for weak typing to 184 // the decoder. 185 // 186 // Note that this is significantly different from the WeakType option 187 // of the Config. 188 func WeaklyTypedHook( 189 f reflect.Kind, 190 t reflect.Kind, 191 data interface{}, 192 ) (interface{}, error) { 193 dataVal := reflect.ValueOf(data) 194 switch t { 195 case reflect.String: 196 switch f { 197 case reflect.Bool: 198 if dataVal.Bool() { 199 return "1", nil 200 } 201 return "0", nil 202 case reflect.Float32: 203 return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil 204 case reflect.Int: 205 return strconv.FormatInt(dataVal.Int(), 10), nil 206 case reflect.Slice: 207 dataType := dataVal.Type() 208 elemKind := dataType.Elem().Kind() 209 if elemKind == reflect.Uint8 { 210 return string(dataVal.Interface().([]uint8)), nil 211 } 212 case reflect.Uint: 213 return strconv.FormatUint(dataVal.Uint(), 10), nil 214 } 215 } 216 217 return data, nil 218 } 219 220 func RecursiveStructToMapHookFunc() HookFunc { 221 return func(f reflect.Value, t reflect.Value) (interface{}, error) { 222 if f.Kind() != reflect.Struct { 223 return f.Interface(), nil 224 } 225 226 var i interface{} = struct{}{} 227 if t.Type() != reflect.TypeOf(&i).Elem() { 228 return f.Interface(), nil 229 } 230 231 m := make(map[string]interface{}) 232 t.Set(reflect.ValueOf(m)) 233 234 return f.Interface(), nil 235 } 236 } 237 238 // TextUnmarshallerHookFunc returns a HookFunc that applies 239 // strings to the UnmarshalText function, when the target type 240 // implements the encoding.TextUnmarshaler interface 241 func TextUnmarshallerHookFunc() HookFuncType { 242 return func( 243 f reflect.Type, 244 t reflect.Type, 245 data interface{}, 246 ) (interface{}, error) { 247 if f.Kind() != reflect.String { 248 return data, nil 249 } 250 result := reflect.New(t).Interface() 251 unmarshaller, ok := result.(encoding.TextUnmarshaler) 252 if !ok { 253 return data, nil 254 } 255 if err := unmarshaller.UnmarshalText([]byte(data.(string))); err != nil { 256 return nil, err 257 } 258 return result, nil 259 } 260 }