1package mapstructure 2 3import ( 4 "errors" 5 "fmt" 6 "net" 7 "reflect" 8 "strconv" 9 "strings" 10 "time" 11) 12 13// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns 14// it into the proper DecodeHookFunc type, such as DecodeHookFuncType. 15func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc { 16 // Create variables here so we can reference them with the reflect pkg 17 var f1 DecodeHookFuncType 18 var f2 DecodeHookFuncKind 19 20 // Fill in the variables into this interface and the rest is done 21 // automatically using the reflect package. 22 potential := []interface{}{f1, f2} 23 24 v := reflect.ValueOf(h) 25 vt := v.Type() 26 for _, raw := range potential { 27 pt := reflect.ValueOf(raw).Type() 28 if vt.ConvertibleTo(pt) { 29 return v.Convert(pt).Interface() 30 } 31 } 32 33 return nil 34} 35 36// DecodeHookExec executes the given decode hook. This should be used 37// since it'll naturally degrade to the older backwards compatible DecodeHookFunc 38// that took reflect.Kind instead of reflect.Type. 39func DecodeHookExec( 40 raw DecodeHookFunc, 41 from reflect.Type, to reflect.Type, 42 data interface{}) (interface{}, error) { 43 switch f := typedDecodeHook(raw).(type) { 44 case DecodeHookFuncType: 45 return f(from, to, data) 46 case DecodeHookFuncKind: 47 return f(from.Kind(), to.Kind(), data) 48 default: 49 return nil, errors.New("invalid decode hook signature") 50 } 51} 52 53// ComposeDecodeHookFunc creates a single DecodeHookFunc that 54// automatically composes multiple DecodeHookFuncs. 55// 56// The composed funcs are called in order, with the result of the 57// previous transformation. 58func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc { 59 return func( 60 f reflect.Type, 61 t reflect.Type, 62 data interface{}) (interface{}, error) { 63 var err error 64 for _, f1 := range fs { 65 data, err = DecodeHookExec(f1, f, t, data) 66 if err != nil { 67 return nil, err 68 } 69 70 // Modify the from kind to be correct with the new data 71 f = nil 72 if val := reflect.ValueOf(data); val.IsValid() { 73 f = val.Type() 74 } 75 } 76 77 return data, nil 78 } 79} 80 81// StringToSliceHookFunc returns a DecodeHookFunc that converts 82// string to []string by splitting on the given sep. 83func StringToSliceHookFunc(sep string) DecodeHookFunc { 84 return func( 85 f reflect.Kind, 86 t reflect.Kind, 87 data interface{}) (interface{}, error) { 88 if f != reflect.String || t != reflect.Slice { 89 return data, nil 90 } 91 92 raw := data.(string) 93 if raw == "" { 94 return []string{}, nil 95 } 96 97 return strings.Split(raw, sep), nil 98 } 99} 100 101// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts 102// strings to time.Duration. 103func StringToTimeDurationHookFunc() DecodeHookFunc { 104 return func( 105 f reflect.Type, 106 t reflect.Type, 107 data interface{}) (interface{}, error) { 108 if f.Kind() != reflect.String { 109 return data, nil 110 } 111 if t != reflect.TypeOf(time.Duration(5)) { 112 return data, nil 113 } 114 115 // Convert it by parsing 116 return time.ParseDuration(data.(string)) 117 } 118} 119 120// StringToIPHookFunc returns a DecodeHookFunc that converts 121// strings to net.IP 122func StringToIPHookFunc() DecodeHookFunc { 123 return func( 124 f reflect.Type, 125 t reflect.Type, 126 data interface{}) (interface{}, error) { 127 if f.Kind() != reflect.String { 128 return data, nil 129 } 130 if t != reflect.TypeOf(net.IP{}) { 131 return data, nil 132 } 133 134 // Convert it by parsing 135 ip := net.ParseIP(data.(string)) 136 if ip == nil { 137 return net.IP{}, fmt.Errorf("failed parsing ip %v", data) 138 } 139 140 return ip, nil 141 } 142} 143 144// StringToIPNetHookFunc returns a DecodeHookFunc that converts 145// strings to net.IPNet 146func StringToIPNetHookFunc() DecodeHookFunc { 147 return func( 148 f reflect.Type, 149 t reflect.Type, 150 data interface{}) (interface{}, error) { 151 if f.Kind() != reflect.String { 152 return data, nil 153 } 154 if t != reflect.TypeOf(net.IPNet{}) { 155 return data, nil 156 } 157 158 // Convert it by parsing 159 _, net, err := net.ParseCIDR(data.(string)) 160 return net, err 161 } 162} 163 164// StringToTimeHookFunc returns a DecodeHookFunc that converts 165// strings to time.Time. 166func StringToTimeHookFunc(layout string) DecodeHookFunc { 167 return func( 168 f reflect.Type, 169 t reflect.Type, 170 data interface{}) (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 DecodeHookFunc which adds support for weak typing to 184// the decoder. 185// 186// Note that this is significantly different from the WeaklyTypedInput option 187// of the DecoderConfig. 188func WeaklyTypedHook( 189 f reflect.Kind, 190 t reflect.Kind, 191 data interface{}) (interface{}, error) { 192 dataVal := reflect.ValueOf(data) 193 switch t { 194 case reflect.String: 195 switch f { 196 case reflect.Bool: 197 if dataVal.Bool() { 198 return "1", nil 199 } 200 return "0", nil 201 case reflect.Float32: 202 return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil 203 case reflect.Int: 204 return strconv.FormatInt(dataVal.Int(), 10), nil 205 case reflect.Slice: 206 dataType := dataVal.Type() 207 elemKind := dataType.Elem().Kind() 208 if elemKind == reflect.Uint8 { 209 return string(dataVal.Interface().([]uint8)), nil 210 } 211 case reflect.Uint: 212 return strconv.FormatUint(dataVal.Uint(), 10), nil 213 } 214 } 215 216 return data, nil 217} 218