full diff: https://github.com/mitchellh/mapstructure/compare/v1.0.0...v1.3.2 v1.3.2 - Decode into interface type with a struct value is supported v1.3.1 - Squash should only squash embedded structs. v1.3.0 - Added `",omitempty"` support. This will ignore zero values in the source structure when encoding. v1.2.3 - Fix duplicate entries in Keys list with pointer values. v1.2.2 - Do not add unsettable (unexported) values to the unused metadata key or "remain" value. v1.2.1 - Go modules checksum mismatch fix v1.2.0 - Added support to capture unused values in a field using the `",remain"` value in the mapstructure tag. There is an example to showcase usage. - Added `DecoderConfig` option to always squash embedded structs - `json.Number` can decode into `uint` types - Empty slices are preserved and not replaced with nil slices - Fix panic that can occur in when decoding a map into a nil slice of structs - Improved package documentation for godoc v1.1.2 - Fix error when decode hook decodes interface implementation into interface type. v1.1.1 - Fix panic that can happen in `decodePtr` v1.1.0 - Added `StringToIPHookFunc` to convert `string` to `net.IP` and `net.IPNet` - Support struct to struct decoding - If source map value is nil, then destination map value is nil (instead of empty) - If source slice value is nil, then destination slice value is nil (instead of empty) - If source pointer is nil, then destination pointer is set to nil (instead of allocated zero value of type) Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
218 lines
5.1 KiB
Go
218 lines
5.1 KiB
Go
package mapstructure
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns
|
|
// it into the proper DecodeHookFunc type, such as DecodeHookFuncType.
|
|
func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
|
|
// Create variables here so we can reference them with the reflect pkg
|
|
var f1 DecodeHookFuncType
|
|
var f2 DecodeHookFuncKind
|
|
|
|
// Fill in the variables into this interface and the rest is done
|
|
// automatically using the reflect package.
|
|
potential := []interface{}{f1, f2}
|
|
|
|
v := reflect.ValueOf(h)
|
|
vt := v.Type()
|
|
for _, raw := range potential {
|
|
pt := reflect.ValueOf(raw).Type()
|
|
if vt.ConvertibleTo(pt) {
|
|
return v.Convert(pt).Interface()
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DecodeHookExec executes the given decode hook. This should be used
|
|
// since it'll naturally degrade to the older backwards compatible DecodeHookFunc
|
|
// that took reflect.Kind instead of reflect.Type.
|
|
func DecodeHookExec(
|
|
raw DecodeHookFunc,
|
|
from reflect.Type, to reflect.Type,
|
|
data interface{}) (interface{}, error) {
|
|
switch f := typedDecodeHook(raw).(type) {
|
|
case DecodeHookFuncType:
|
|
return f(from, to, data)
|
|
case DecodeHookFuncKind:
|
|
return f(from.Kind(), to.Kind(), data)
|
|
default:
|
|
return nil, errors.New("invalid decode hook signature")
|
|
}
|
|
}
|
|
|
|
// ComposeDecodeHookFunc creates a single DecodeHookFunc that
|
|
// automatically composes multiple DecodeHookFuncs.
|
|
//
|
|
// The composed funcs are called in order, with the result of the
|
|
// previous transformation.
|
|
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
|
|
return func(
|
|
f reflect.Type,
|
|
t reflect.Type,
|
|
data interface{}) (interface{}, error) {
|
|
var err error
|
|
for _, f1 := range fs {
|
|
data, err = DecodeHookExec(f1, f, t, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Modify the from kind to be correct with the new data
|
|
f = nil
|
|
if val := reflect.ValueOf(data); val.IsValid() {
|
|
f = val.Type()
|
|
}
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
// StringToSliceHookFunc returns a DecodeHookFunc that converts
|
|
// string to []string by splitting on the given sep.
|
|
func StringToSliceHookFunc(sep string) DecodeHookFunc {
|
|
return func(
|
|
f reflect.Kind,
|
|
t reflect.Kind,
|
|
data interface{}) (interface{}, error) {
|
|
if f != reflect.String || t != reflect.Slice {
|
|
return data, nil
|
|
}
|
|
|
|
raw := data.(string)
|
|
if raw == "" {
|
|
return []string{}, nil
|
|
}
|
|
|
|
return strings.Split(raw, sep), nil
|
|
}
|
|
}
|
|
|
|
// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
|
|
// strings to time.Duration.
|
|
func StringToTimeDurationHookFunc() DecodeHookFunc {
|
|
return func(
|
|
f reflect.Type,
|
|
t reflect.Type,
|
|
data interface{}) (interface{}, error) {
|
|
if f.Kind() != reflect.String {
|
|
return data, nil
|
|
}
|
|
if t != reflect.TypeOf(time.Duration(5)) {
|
|
return data, nil
|
|
}
|
|
|
|
// Convert it by parsing
|
|
return time.ParseDuration(data.(string))
|
|
}
|
|
}
|
|
|
|
// StringToIPHookFunc returns a DecodeHookFunc that converts
|
|
// strings to net.IP
|
|
func StringToIPHookFunc() DecodeHookFunc {
|
|
return func(
|
|
f reflect.Type,
|
|
t reflect.Type,
|
|
data interface{}) (interface{}, error) {
|
|
if f.Kind() != reflect.String {
|
|
return data, nil
|
|
}
|
|
if t != reflect.TypeOf(net.IP{}) {
|
|
return data, nil
|
|
}
|
|
|
|
// Convert it by parsing
|
|
ip := net.ParseIP(data.(string))
|
|
if ip == nil {
|
|
return net.IP{}, fmt.Errorf("failed parsing ip %v", data)
|
|
}
|
|
|
|
return ip, nil
|
|
}
|
|
}
|
|
|
|
// StringToIPNetHookFunc returns a DecodeHookFunc that converts
|
|
// strings to net.IPNet
|
|
func StringToIPNetHookFunc() DecodeHookFunc {
|
|
return func(
|
|
f reflect.Type,
|
|
t reflect.Type,
|
|
data interface{}) (interface{}, error) {
|
|
if f.Kind() != reflect.String {
|
|
return data, nil
|
|
}
|
|
if t != reflect.TypeOf(net.IPNet{}) {
|
|
return data, nil
|
|
}
|
|
|
|
// Convert it by parsing
|
|
_, net, err := net.ParseCIDR(data.(string))
|
|
return net, err
|
|
}
|
|
}
|
|
|
|
// StringToTimeHookFunc returns a DecodeHookFunc that converts
|
|
// strings to time.Time.
|
|
func StringToTimeHookFunc(layout string) DecodeHookFunc {
|
|
return func(
|
|
f reflect.Type,
|
|
t reflect.Type,
|
|
data interface{}) (interface{}, error) {
|
|
if f.Kind() != reflect.String {
|
|
return data, nil
|
|
}
|
|
if t != reflect.TypeOf(time.Time{}) {
|
|
return data, nil
|
|
}
|
|
|
|
// Convert it by parsing
|
|
return time.Parse(layout, data.(string))
|
|
}
|
|
}
|
|
|
|
// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to
|
|
// the decoder.
|
|
//
|
|
// Note that this is significantly different from the WeaklyTypedInput option
|
|
// of the DecoderConfig.
|
|
func WeaklyTypedHook(
|
|
f reflect.Kind,
|
|
t reflect.Kind,
|
|
data interface{}) (interface{}, error) {
|
|
dataVal := reflect.ValueOf(data)
|
|
switch t {
|
|
case reflect.String:
|
|
switch f {
|
|
case reflect.Bool:
|
|
if dataVal.Bool() {
|
|
return "1", nil
|
|
}
|
|
return "0", nil
|
|
case reflect.Float32:
|
|
return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
|
|
case reflect.Int:
|
|
return strconv.FormatInt(dataVal.Int(), 10), nil
|
|
case reflect.Slice:
|
|
dataType := dataVal.Type()
|
|
elemKind := dataType.Elem().Kind()
|
|
if elemKind == reflect.Uint8 {
|
|
return string(dataVal.Interface().([]uint8)), nil
|
|
}
|
|
case reflect.Uint:
|
|
return strconv.FormatUint(dataVal.Uint(), 10), nil
|
|
}
|
|
}
|
|
|
|
return data, nil
|
|
}
|