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