decode_hooks.go

  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}