decode_hooks.go raw

   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  }
 280