decode_hooks.go (6908B)
1 package mapstructure 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 DecodeHookFunc (an interface{}) and turns 15 // it into the proper DecodeHookFunc type, such as DecodeHookFuncType. 16 func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc { 17 // Create variables here so we can reference them with the reflect pkg 18 var f1 DecodeHookFuncType 19 var f2 DecodeHookFuncKind 20 var f3 DecodeHookFuncValue 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 DecodeHookFunc 40 // that took reflect.Kind instead of reflect.Type. 41 func DecodeHookExec( 42 raw DecodeHookFunc, 43 from reflect.Value, to reflect.Value) (interface{}, error) { 44 45 switch f := typedDecodeHook(raw).(type) { 46 case DecodeHookFuncType: 47 return f(from.Type(), to.Type(), from.Interface()) 48 case DecodeHookFuncKind: 49 return f(from.Kind(), to.Kind(), from.Interface()) 50 case DecodeHookFuncValue: 51 return f(from, to) 52 default: 53 return nil, errors.New("invalid decode hook signature") 54 } 55 } 56 57 // ComposeDecodeHookFunc creates a single DecodeHookFunc that 58 // automatically composes multiple DecodeHookFuncs. 59 // 60 // The composed funcs are called in order, with the result of the 61 // previous transformation. 62 func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc { 63 return func(f reflect.Value, t reflect.Value) (interface{}, error) { 64 var err error 65 data := f.Interface() 66 67 newFrom := f 68 for _, f1 := range fs { 69 data, err = DecodeHookExec(f1, newFrom, t) 70 if err != nil { 71 return nil, err 72 } 73 newFrom = reflect.ValueOf(data) 74 } 75 76 return data, nil 77 } 78 } 79 80 // OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned. 81 // If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages. 82 func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc { 83 return func(a, b reflect.Value) (interface{}, error) { 84 var allErrs string 85 var out interface{} 86 var err error 87 88 for _, f := range ff { 89 out, err = DecodeHookExec(f, a, b) 90 if err != nil { 91 allErrs += err.Error() + "\n" 92 continue 93 } 94 95 return out, nil 96 } 97 98 return nil, errors.New(allErrs) 99 } 100 } 101 102 // StringToSliceHookFunc returns a DecodeHookFunc that converts 103 // string to []string by splitting on the given sep. 104 func StringToSliceHookFunc(sep string) DecodeHookFunc { 105 return func( 106 f reflect.Kind, 107 t reflect.Kind, 108 data interface{}) (interface{}, error) { 109 if f != reflect.String || t != reflect.Slice { 110 return data, nil 111 } 112 113 raw := data.(string) 114 if raw == "" { 115 return []string{}, nil 116 } 117 118 return strings.Split(raw, sep), nil 119 } 120 } 121 122 // StringToTimeDurationHookFunc returns a DecodeHookFunc that converts 123 // strings to time.Duration. 124 func StringToTimeDurationHookFunc() DecodeHookFunc { 125 return func( 126 f reflect.Type, 127 t reflect.Type, 128 data interface{}) (interface{}, error) { 129 if f.Kind() != reflect.String { 130 return data, nil 131 } 132 if t != reflect.TypeOf(time.Duration(5)) { 133 return data, nil 134 } 135 136 // Convert it by parsing 137 return time.ParseDuration(data.(string)) 138 } 139 } 140 141 // StringToIPHookFunc returns a DecodeHookFunc that converts 142 // strings to net.IP 143 func StringToIPHookFunc() DecodeHookFunc { 144 return func( 145 f reflect.Type, 146 t reflect.Type, 147 data interface{}) (interface{}, error) { 148 if f.Kind() != reflect.String { 149 return data, nil 150 } 151 if t != reflect.TypeOf(net.IP{}) { 152 return data, nil 153 } 154 155 // Convert it by parsing 156 ip := net.ParseIP(data.(string)) 157 if ip == nil { 158 return net.IP{}, fmt.Errorf("failed parsing ip %v", data) 159 } 160 161 return ip, nil 162 } 163 } 164 165 // StringToIPNetHookFunc returns a DecodeHookFunc that converts 166 // strings to net.IPNet 167 func StringToIPNetHookFunc() DecodeHookFunc { 168 return func( 169 f reflect.Type, 170 t reflect.Type, 171 data interface{}) (interface{}, error) { 172 if f.Kind() != reflect.String { 173 return data, nil 174 } 175 if t != reflect.TypeOf(net.IPNet{}) { 176 return data, nil 177 } 178 179 // Convert it by parsing 180 _, net, err := net.ParseCIDR(data.(string)) 181 return net, err 182 } 183 } 184 185 // StringToTimeHookFunc returns a DecodeHookFunc that converts 186 // strings to time.Time. 187 func StringToTimeHookFunc(layout string) DecodeHookFunc { 188 return func( 189 f reflect.Type, 190 t reflect.Type, 191 data interface{}) (interface{}, error) { 192 if f.Kind() != reflect.String { 193 return data, nil 194 } 195 if t != reflect.TypeOf(time.Time{}) { 196 return data, nil 197 } 198 199 // Convert it by parsing 200 return time.Parse(layout, data.(string)) 201 } 202 } 203 204 // WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to 205 // the decoder. 206 // 207 // Note that this is significantly different from the WeaklyTypedInput option 208 // of the DecoderConfig. 209 func WeaklyTypedHook( 210 f reflect.Kind, 211 t reflect.Kind, 212 data interface{}) (interface{}, error) { 213 dataVal := reflect.ValueOf(data) 214 switch t { 215 case reflect.String: 216 switch f { 217 case reflect.Bool: 218 if dataVal.Bool() { 219 return "1", nil 220 } 221 return "0", nil 222 case reflect.Float32: 223 return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil 224 case reflect.Int: 225 return strconv.FormatInt(dataVal.Int(), 10), nil 226 case reflect.Slice: 227 dataType := dataVal.Type() 228 elemKind := dataType.Elem().Kind() 229 if elemKind == reflect.Uint8 { 230 return string(dataVal.Interface().([]uint8)), nil 231 } 232 case reflect.Uint: 233 return strconv.FormatUint(dataVal.Uint(), 10), nil 234 } 235 } 236 237 return data, nil 238 } 239 240 func RecursiveStructToMapHookFunc() DecodeHookFunc { 241 return func(f reflect.Value, t reflect.Value) (interface{}, error) { 242 if f.Kind() != reflect.Struct { 243 return f.Interface(), nil 244 } 245 246 var i interface{} = struct{}{} 247 if t.Type() != reflect.TypeOf(&i).Elem() { 248 return f.Interface(), nil 249 } 250 251 m := make(map[string]interface{}) 252 t.Set(reflect.ValueOf(m)) 253 254 return f.Interface(), nil 255 } 256 } 257 258 // TextUnmarshallerHookFunc returns a DecodeHookFunc that applies 259 // strings to the UnmarshalText function, when the target type 260 // implements the encoding.TextUnmarshaler interface 261 func TextUnmarshallerHookFunc() DecodeHookFuncType { 262 return func( 263 f reflect.Type, 264 t reflect.Type, 265 data interface{}) (interface{}, error) { 266 if f.Kind() != reflect.String { 267 return data, nil 268 } 269 result := reflect.New(t).Interface() 270 unmarshaller, ok := result.(encoding.TextUnmarshaler) 271 if !ok { 272 return data, nil 273 } 274 if err := unmarshaller.UnmarshalText([]byte(data.(string))); err != nil { 275 return nil, err 276 } 277 return result, nil 278 } 279 }