pax_global_header00006660000000000000000000000064133555667270014535gustar00rootroot0000000000000052 comment=3536a929edddb9a5b34bd6861dc4a9647cb459fe mapstructure-1.1.2/000077500000000000000000000000001335556672700142745ustar00rootroot00000000000000mapstructure-1.1.2/.travis.yml000066400000000000000000000000741335556672700164060ustar00rootroot00000000000000language: go go: - "1.11.x" - tip script: - go test mapstructure-1.1.2/CHANGELOG.md000066400000000000000000000011611335556672700161040ustar00rootroot00000000000000## 1.1.2 * Fix error when decode hook decodes interface implementation into interface type. [GH-140] ## 1.1.1 * Fix panic that can happen in `decodePtr` ## 1.1.0 * Added `StringToIPHookFunc` to convert `string` to `net.IP` and `net.IPNet` [GH-133] * Support struct to struct decoding [GH-137] * 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) ## 1.0.0 * Initial tagged stable release. mapstructure-1.1.2/LICENSE000066400000000000000000000020751335556672700153050ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2013 Mitchell Hashimoto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. mapstructure-1.1.2/README.md000066400000000000000000000032161335556672700155550ustar00rootroot00000000000000# mapstructure [![Godoc](https://godoc.org/github.com/mitchellh/mapstructure?status.svg)](https://godoc.org/github.com/mitchellh/mapstructure) mapstructure is a Go library for decoding generic map values to structures and vice versa, while providing helpful error handling. This library is most useful when decoding values from some data stream (JSON, Gob, etc.) where you don't _quite_ know the structure of the underlying data until you read a part of it. You can therefore read a `map[string]interface{}` and use this library to decode it into the proper underlying native Go structure. ## Installation Standard `go get`: ``` $ go get github.com/mitchellh/mapstructure ``` ## Usage & Example For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/mapstructure). The `Decode` function has examples associated with it there. ## But Why?! Go offers fantastic standard libraries for decoding formats such as JSON. The standard method is to have a struct pre-created, and populate that struct from the bytes of the encoded format. This is great, but the problem is if you have configuration or an encoding that changes slightly depending on specific fields. For example, consider this JSON: ```json { "type": "person", "name": "Mitchell" } ``` Perhaps we can't populate a specific structure without first reading the "type" field from the JSON. We could always do two passes over the decoding of the JSON (reading the "type" first, and the rest later). However, it is much simpler to just decode this into a `map[string]interface{}` structure, read the "type" key, then use something like this library to decode it into the proper structure. mapstructure-1.1.2/decode_hooks.go000066400000000000000000000121451335556672700172540ustar00rootroot00000000000000package 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 } mapstructure-1.1.2/decode_hooks_test.go000066400000000000000000000146401335556672700203150ustar00rootroot00000000000000package mapstructure import ( "errors" "net" "reflect" "testing" "time" ) func TestComposeDecodeHookFunc(t *testing.T) { f1 := func( f reflect.Kind, t reflect.Kind, data interface{}) (interface{}, error) { return data.(string) + "foo", nil } f2 := func( f reflect.Kind, t reflect.Kind, data interface{}) (interface{}, error) { return data.(string) + "bar", nil } f := ComposeDecodeHookFunc(f1, f2) result, err := DecodeHookExec( f, reflect.TypeOf(""), reflect.TypeOf([]byte("")), "") if err != nil { t.Fatalf("bad: %s", err) } if result.(string) != "foobar" { t.Fatalf("bad: %#v", result) } } func TestComposeDecodeHookFunc_err(t *testing.T) { f1 := func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) { return nil, errors.New("foo") } f2 := func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) { panic("NOPE") } f := ComposeDecodeHookFunc(f1, f2) _, err := DecodeHookExec( f, reflect.TypeOf(""), reflect.TypeOf([]byte("")), 42) if err.Error() != "foo" { t.Fatalf("bad: %s", err) } } func TestComposeDecodeHookFunc_kinds(t *testing.T) { var f2From reflect.Kind f1 := func( f reflect.Kind, t reflect.Kind, data interface{}) (interface{}, error) { return int(42), nil } f2 := func( f reflect.Kind, t reflect.Kind, data interface{}) (interface{}, error) { f2From = f return data, nil } f := ComposeDecodeHookFunc(f1, f2) _, err := DecodeHookExec( f, reflect.TypeOf(""), reflect.TypeOf([]byte("")), "") if err != nil { t.Fatalf("bad: %s", err) } if f2From != reflect.Int { t.Fatalf("bad: %#v", f2From) } } func TestStringToSliceHookFunc(t *testing.T) { f := StringToSliceHookFunc(",") strType := reflect.TypeOf("") sliceType := reflect.TypeOf([]byte("")) cases := []struct { f, t reflect.Type data interface{} result interface{} err bool }{ {sliceType, sliceType, 42, 42, false}, {strType, strType, 42, 42, false}, { strType, sliceType, "foo,bar,baz", []string{"foo", "bar", "baz"}, false, }, { strType, sliceType, "", []string{}, false, }, } for i, tc := range cases { actual, err := DecodeHookExec(f, tc.f, tc.t, tc.data) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToTimeDurationHookFunc(t *testing.T) { f := StringToTimeDurationHookFunc() strType := reflect.TypeOf("") timeType := reflect.TypeOf(time.Duration(5)) cases := []struct { f, t reflect.Type data interface{} result interface{} err bool }{ {strType, timeType, "5s", 5 * time.Second, false}, {strType, timeType, "5", time.Duration(0), true}, {strType, strType, "5", "5", false}, } for i, tc := range cases { actual, err := DecodeHookExec(f, tc.f, tc.t, tc.data) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToTimeHookFunc(t *testing.T) { strType := reflect.TypeOf("") timeType := reflect.TypeOf(time.Time{}) cases := []struct { f, t reflect.Type layout string data interface{} result interface{} err bool }{ {strType, timeType, time.RFC3339, "2006-01-02T15:04:05Z", time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC), false}, {strType, timeType, time.RFC3339, "5", time.Time{}, true}, {strType, strType, time.RFC3339, "5", "5", false}, } for i, tc := range cases { f := StringToTimeHookFunc(tc.layout) actual, err := DecodeHookExec(f, tc.f, tc.t, tc.data) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToIPHookFunc(t *testing.T) { strType := reflect.TypeOf("") ipType := reflect.TypeOf(net.IP{}) cases := []struct { f, t reflect.Type data interface{} result interface{} err bool }{ {strType, ipType, "1.2.3.4", net.IPv4(0x01, 0x02, 0x03, 0x04), false}, {strType, ipType, "5", net.IP{}, true}, {strType, strType, "5", "5", false}, } for i, tc := range cases { f := StringToIPHookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t, tc.data) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToIPNetHookFunc(t *testing.T) { strType := reflect.TypeOf("") ipNetType := reflect.TypeOf(net.IPNet{}) var nilNet *net.IPNet = nil cases := []struct { f, t reflect.Type data interface{} result interface{} err bool }{ {strType, ipNetType, "1.2.3.4/24", &net.IPNet{ IP: net.IP{0x01, 0x02, 0x03, 0x00}, Mask: net.IPv4Mask(0xff, 0xff, 0xff, 0x00), }, false}, {strType, ipNetType, "5", nilNet, true}, {strType, strType, "5", "5", false}, } for i, tc := range cases { f := StringToIPNetHookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t, tc.data) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestWeaklyTypedHook(t *testing.T) { var f DecodeHookFunc = WeaklyTypedHook boolType := reflect.TypeOf(true) strType := reflect.TypeOf("") sliceType := reflect.TypeOf([]byte("")) cases := []struct { f, t reflect.Type data interface{} result interface{} err bool }{ // TO STRING { boolType, strType, false, "0", false, }, { boolType, strType, true, "1", false, }, { reflect.TypeOf(float32(1)), strType, float32(7), "7", false, }, { reflect.TypeOf(int(1)), strType, int(7), "7", false, }, { sliceType, strType, []uint8("foo"), "foo", false, }, { reflect.TypeOf(uint(1)), strType, uint(7), "7", false, }, } for i, tc := range cases { actual, err := DecodeHookExec(f, tc.f, tc.t, tc.data) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } mapstructure-1.1.2/error.go000066400000000000000000000020051335556672700157510ustar00rootroot00000000000000package mapstructure import ( "errors" "fmt" "sort" "strings" ) // Error implements the error interface and can represents multiple // errors that occur in the course of a single decode. type Error struct { Errors []string } func (e *Error) Error() string { points := make([]string, len(e.Errors)) for i, err := range e.Errors { points[i] = fmt.Sprintf("* %s", err) } sort.Strings(points) return fmt.Sprintf( "%d error(s) decoding:\n\n%s", len(e.Errors), strings.Join(points, "\n")) } // WrappedErrors implements the errwrap.Wrapper interface to make this // return value more useful with the errwrap and go-multierror libraries. func (e *Error) WrappedErrors() []error { if e == nil { return nil } result := make([]error, len(e.Errors)) for i, e := range e.Errors { result[i] = errors.New(e) } return result } func appendErrors(errors []string, err error) []string { switch e := err.(type) { case *Error: return append(errors, e.Errors...) default: return append(errors, e.Error()) } } mapstructure-1.1.2/go.mod000066400000000000000000000000511335556672700153760ustar00rootroot00000000000000module github.com/mitchellh/mapstructure mapstructure-1.1.2/mapstructure.go000066400000000000000000000772341335556672700173760ustar00rootroot00000000000000// Package mapstructure exposes functionality to convert an arbitrary // map[string]interface{} into a native Go structure. // // The Go structure can be arbitrarily complex, containing slices, // other structs, etc. and the decoder will properly decode nested // maps and so on into the proper structures in the native Go struct. // See the examples to see what the decoder is capable of. package mapstructure import ( "encoding/json" "errors" "fmt" "reflect" "sort" "strconv" "strings" ) // DecodeHookFunc is the callback function that can be used for // data transformations. See "DecodeHook" in the DecoderConfig // struct. // // The type should be DecodeHookFuncType or DecodeHookFuncKind. // Either is accepted. Types are a superset of Kinds (Types can return // Kinds) and are generally a richer thing to use, but Kinds are simpler // if you only need those. // // The reason DecodeHookFunc is multi-typed is for backwards compatibility: // we started with Kinds and then realized Types were the better solution, // but have a promise to not break backwards compat so we now support // both. type DecodeHookFunc interface{} // DecodeHookFuncType is a DecodeHookFunc which has complete information about // the source and target types. type DecodeHookFuncType func(reflect.Type, reflect.Type, interface{}) (interface{}, error) // DecodeHookFuncKind is a DecodeHookFunc which knows only the Kinds of the // source and target types. type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) // DecoderConfig is the configuration that is used to create a new decoder // and allows customization of various aspects of decoding. type DecoderConfig struct { // DecodeHook, if set, will be called before any decoding and any // type conversion (if WeaklyTypedInput is on). This lets you modify // the values before they're set down onto the resulting struct. // // If an error is returned, the entire decode will fail with that // error. DecodeHook DecodeHookFunc // If ErrorUnused is true, then it is an error for there to exist // keys in the original map that were unused in the decoding process // (extra keys). ErrorUnused bool // ZeroFields, if set to true, will zero fields before writing them. // For example, a map will be emptied before decoded values are put in // it. If this is false, a map will be merged. ZeroFields bool // If WeaklyTypedInput is true, the decoder will make the following // "weak" conversions: // // - bools to string (true = "1", false = "0") // - numbers to string (base 10) // - bools to int/uint (true = 1, false = 0) // - strings to int/uint (base implied by prefix) // - int to bool (true if value != 0) // - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F, // FALSE, false, False. Anything else is an error) // - empty array = empty map and vice versa // - negative numbers to overflowed uint values (base 10) // - slice of maps to a merged map // - single values are converted to slices if required. Each // element is weakly decoded. For example: "4" can become []int{4} // if the target type is an int slice. // WeaklyTypedInput bool // Metadata is the struct that will contain extra metadata about // the decoding. If this is nil, then no metadata will be tracked. Metadata *Metadata // Result is a pointer to the struct that will contain the decoded // value. Result interface{} // The tag name that mapstructure reads for field names. This // defaults to "mapstructure" TagName string } // A Decoder takes a raw interface value and turns it into structured // data, keeping track of rich error information along the way in case // anything goes wrong. Unlike the basic top-level Decode method, you can // more finely control how the Decoder behaves using the DecoderConfig // structure. The top-level Decode method is just a convenience that sets // up the most basic Decoder. type Decoder struct { config *DecoderConfig } // Metadata contains information about decoding a structure that // is tedious or difficult to get otherwise. type Metadata struct { // Keys are the keys of the structure which were successfully decoded Keys []string // Unused is a slice of keys that were found in the raw value but // weren't decoded since there was no matching field in the result interface Unused []string } // Decode takes an input structure and uses reflection to translate it to // the output structure. output must be a pointer to a map or struct. func Decode(input interface{}, output interface{}) error { config := &DecoderConfig{ Metadata: nil, Result: output, } decoder, err := NewDecoder(config) if err != nil { return err } return decoder.Decode(input) } // WeakDecode is the same as Decode but is shorthand to enable // WeaklyTypedInput. See DecoderConfig for more info. func WeakDecode(input, output interface{}) error { config := &DecoderConfig{ Metadata: nil, Result: output, WeaklyTypedInput: true, } decoder, err := NewDecoder(config) if err != nil { return err } return decoder.Decode(input) } // DecodeMetadata is the same as Decode, but is shorthand to // enable metadata collection. See DecoderConfig for more info. func DecodeMetadata(input interface{}, output interface{}, metadata *Metadata) error { config := &DecoderConfig{ Metadata: metadata, Result: output, } decoder, err := NewDecoder(config) if err != nil { return err } return decoder.Decode(input) } // WeakDecodeMetadata is the same as Decode, but is shorthand to // enable both WeaklyTypedInput and metadata collection. See // DecoderConfig for more info. func WeakDecodeMetadata(input interface{}, output interface{}, metadata *Metadata) error { config := &DecoderConfig{ Metadata: metadata, Result: output, WeaklyTypedInput: true, } decoder, err := NewDecoder(config) if err != nil { return err } return decoder.Decode(input) } // NewDecoder returns a new decoder for the given configuration. Once // a decoder has been returned, the same configuration must not be used // again. func NewDecoder(config *DecoderConfig) (*Decoder, error) { val := reflect.ValueOf(config.Result) if val.Kind() != reflect.Ptr { return nil, errors.New("result must be a pointer") } val = val.Elem() if !val.CanAddr() { return nil, errors.New("result must be addressable (a pointer)") } if config.Metadata != nil { if config.Metadata.Keys == nil { config.Metadata.Keys = make([]string, 0) } if config.Metadata.Unused == nil { config.Metadata.Unused = make([]string, 0) } } if config.TagName == "" { config.TagName = "mapstructure" } result := &Decoder{ config: config, } return result, nil } // Decode decodes the given raw interface to the target pointer specified // by the configuration. func (d *Decoder) Decode(input interface{}) error { return d.decode("", input, reflect.ValueOf(d.config.Result).Elem()) } // Decodes an unknown data type into a specific reflection value. func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error { var inputVal reflect.Value if input != nil { inputVal = reflect.ValueOf(input) // We need to check here if input is a typed nil. Typed nils won't // match the "input == nil" below so we check that here. if inputVal.Kind() == reflect.Ptr && inputVal.IsNil() { input = nil } } if input == nil { // If the data is nil, then we don't set anything, unless ZeroFields is set // to true. if d.config.ZeroFields { outVal.Set(reflect.Zero(outVal.Type())) if d.config.Metadata != nil && name != "" { d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) } } return nil } if !inputVal.IsValid() { // If the input value is invalid, then we just set the value // to be the zero value. outVal.Set(reflect.Zero(outVal.Type())) if d.config.Metadata != nil && name != "" { d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) } return nil } if d.config.DecodeHook != nil { // We have a DecodeHook, so let's pre-process the input. var err error input, err = DecodeHookExec( d.config.DecodeHook, inputVal.Type(), outVal.Type(), input) if err != nil { return fmt.Errorf("error decoding '%s': %s", name, err) } } var err error outputKind := getKind(outVal) switch outputKind { case reflect.Bool: err = d.decodeBool(name, input, outVal) case reflect.Interface: err = d.decodeBasic(name, input, outVal) case reflect.String: err = d.decodeString(name, input, outVal) case reflect.Int: err = d.decodeInt(name, input, outVal) case reflect.Uint: err = d.decodeUint(name, input, outVal) case reflect.Float32: err = d.decodeFloat(name, input, outVal) case reflect.Struct: err = d.decodeStruct(name, input, outVal) case reflect.Map: err = d.decodeMap(name, input, outVal) case reflect.Ptr: err = d.decodePtr(name, input, outVal) case reflect.Slice: err = d.decodeSlice(name, input, outVal) case reflect.Array: err = d.decodeArray(name, input, outVal) case reflect.Func: err = d.decodeFunc(name, input, outVal) default: // If we reached this point then we weren't able to decode it return fmt.Errorf("%s: unsupported type: %s", name, outputKind) } // If we reached here, then we successfully decoded SOMETHING, so // mark the key as used if we're tracking metainput. if d.config.Metadata != nil && name != "" { d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) } return err } // This decodes a basic type (bool, int, string, etc.) and sets the // value to "data" of that type. func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error { if val.IsValid() && val.Elem().IsValid() { return d.decode(name, data, val.Elem()) } dataVal := reflect.ValueOf(data) // If the input data is a pointer, and the assigned type is the dereference // of that exact pointer, then indirect it so that we can assign it. // Example: *string to string if dataVal.Kind() == reflect.Ptr && dataVal.Type().Elem() == val.Type() { dataVal = reflect.Indirect(dataVal) } if !dataVal.IsValid() { dataVal = reflect.Zero(val.Type()) } dataValType := dataVal.Type() if !dataValType.AssignableTo(val.Type()) { return fmt.Errorf( "'%s' expected type '%s', got '%s'", name, val.Type(), dataValType) } val.Set(dataVal) return nil } func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) converted := true switch { case dataKind == reflect.String: val.SetString(dataVal.String()) case dataKind == reflect.Bool && d.config.WeaklyTypedInput: if dataVal.Bool() { val.SetString("1") } else { val.SetString("0") } case dataKind == reflect.Int && d.config.WeaklyTypedInput: val.SetString(strconv.FormatInt(dataVal.Int(), 10)) case dataKind == reflect.Uint && d.config.WeaklyTypedInput: val.SetString(strconv.FormatUint(dataVal.Uint(), 10)) case dataKind == reflect.Float32 && d.config.WeaklyTypedInput: val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64)) case dataKind == reflect.Slice && d.config.WeaklyTypedInput, dataKind == reflect.Array && d.config.WeaklyTypedInput: dataType := dataVal.Type() elemKind := dataType.Elem().Kind() switch elemKind { case reflect.Uint8: var uints []uint8 if dataKind == reflect.Array { uints = make([]uint8, dataVal.Len(), dataVal.Len()) for i := range uints { uints[i] = dataVal.Index(i).Interface().(uint8) } } else { uints = dataVal.Interface().([]uint8) } val.SetString(string(uints)) default: converted = false } default: converted = false } if !converted { return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s'", name, val.Type(), dataVal.Type()) } return nil } func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) dataType := dataVal.Type() switch { case dataKind == reflect.Int: val.SetInt(dataVal.Int()) case dataKind == reflect.Uint: val.SetInt(int64(dataVal.Uint())) case dataKind == reflect.Float32: val.SetInt(int64(dataVal.Float())) case dataKind == reflect.Bool && d.config.WeaklyTypedInput: if dataVal.Bool() { val.SetInt(1) } else { val.SetInt(0) } case dataKind == reflect.String && d.config.WeaklyTypedInput: i, err := strconv.ParseInt(dataVal.String(), 0, val.Type().Bits()) if err == nil { val.SetInt(i) } else { return fmt.Errorf("cannot parse '%s' as int: %s", name, err) } case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": jn := data.(json.Number) i, err := jn.Int64() if err != nil { return fmt.Errorf( "error decoding json.Number into %s: %s", name, err) } val.SetInt(i) default: return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s'", name, val.Type(), dataVal.Type()) } return nil } func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) switch { case dataKind == reflect.Int: i := dataVal.Int() if i < 0 && !d.config.WeaklyTypedInput { return fmt.Errorf("cannot parse '%s', %d overflows uint", name, i) } val.SetUint(uint64(i)) case dataKind == reflect.Uint: val.SetUint(dataVal.Uint()) case dataKind == reflect.Float32: f := dataVal.Float() if f < 0 && !d.config.WeaklyTypedInput { return fmt.Errorf("cannot parse '%s', %f overflows uint", name, f) } val.SetUint(uint64(f)) case dataKind == reflect.Bool && d.config.WeaklyTypedInput: if dataVal.Bool() { val.SetUint(1) } else { val.SetUint(0) } case dataKind == reflect.String && d.config.WeaklyTypedInput: i, err := strconv.ParseUint(dataVal.String(), 0, val.Type().Bits()) if err == nil { val.SetUint(i) } else { return fmt.Errorf("cannot parse '%s' as uint: %s", name, err) } default: return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s'", name, val.Type(), dataVal.Type()) } return nil } func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) switch { case dataKind == reflect.Bool: val.SetBool(dataVal.Bool()) case dataKind == reflect.Int && d.config.WeaklyTypedInput: val.SetBool(dataVal.Int() != 0) case dataKind == reflect.Uint && d.config.WeaklyTypedInput: val.SetBool(dataVal.Uint() != 0) case dataKind == reflect.Float32 && d.config.WeaklyTypedInput: val.SetBool(dataVal.Float() != 0) case dataKind == reflect.String && d.config.WeaklyTypedInput: b, err := strconv.ParseBool(dataVal.String()) if err == nil { val.SetBool(b) } else if dataVal.String() == "" { val.SetBool(false) } else { return fmt.Errorf("cannot parse '%s' as bool: %s", name, err) } default: return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s'", name, val.Type(), dataVal.Type()) } return nil } func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) dataType := dataVal.Type() switch { case dataKind == reflect.Int: val.SetFloat(float64(dataVal.Int())) case dataKind == reflect.Uint: val.SetFloat(float64(dataVal.Uint())) case dataKind == reflect.Float32: val.SetFloat(dataVal.Float()) case dataKind == reflect.Bool && d.config.WeaklyTypedInput: if dataVal.Bool() { val.SetFloat(1) } else { val.SetFloat(0) } case dataKind == reflect.String && d.config.WeaklyTypedInput: f, err := strconv.ParseFloat(dataVal.String(), val.Type().Bits()) if err == nil { val.SetFloat(f) } else { return fmt.Errorf("cannot parse '%s' as float: %s", name, err) } case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": jn := data.(json.Number) i, err := jn.Float64() if err != nil { return fmt.Errorf( "error decoding json.Number into %s: %s", name, err) } val.SetFloat(i) default: return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s'", name, val.Type(), dataVal.Type()) } return nil } func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error { valType := val.Type() valKeyType := valType.Key() valElemType := valType.Elem() // By default we overwrite keys in the current map valMap := val // If the map is nil or we're purposely zeroing fields, make a new map if valMap.IsNil() || d.config.ZeroFields { // Make a new map to hold our result mapType := reflect.MapOf(valKeyType, valElemType) valMap = reflect.MakeMap(mapType) } // Check input type and based on the input type jump to the proper func dataVal := reflect.Indirect(reflect.ValueOf(data)) switch dataVal.Kind() { case reflect.Map: return d.decodeMapFromMap(name, dataVal, val, valMap) case reflect.Struct: return d.decodeMapFromStruct(name, dataVal, val, valMap) case reflect.Array, reflect.Slice: if d.config.WeaklyTypedInput { return d.decodeMapFromSlice(name, dataVal, val, valMap) } fallthrough default: return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) } } func (d *Decoder) decodeMapFromSlice(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { // Special case for BC reasons (covered by tests) if dataVal.Len() == 0 { val.Set(valMap) return nil } for i := 0; i < dataVal.Len(); i++ { err := d.decode( fmt.Sprintf("%s[%d]", name, i), dataVal.Index(i).Interface(), val) if err != nil { return err } } return nil } func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { valType := val.Type() valKeyType := valType.Key() valElemType := valType.Elem() // Accumulate errors errors := make([]string, 0) // If the input data is empty, then we just match what the input data is. if dataVal.Len() == 0 { if dataVal.IsNil() { if !val.IsNil() { val.Set(dataVal) } } else { // Set to empty allocated value val.Set(valMap) } return nil } for _, k := range dataVal.MapKeys() { fieldName := fmt.Sprintf("%s[%s]", name, k) // First decode the key into the proper type currentKey := reflect.Indirect(reflect.New(valKeyType)) if err := d.decode(fieldName, k.Interface(), currentKey); err != nil { errors = appendErrors(errors, err) continue } // Next decode the data into the proper type v := dataVal.MapIndex(k).Interface() currentVal := reflect.Indirect(reflect.New(valElemType)) if err := d.decode(fieldName, v, currentVal); err != nil { errors = appendErrors(errors, err) continue } valMap.SetMapIndex(currentKey, currentVal) } // Set the built up map to the value val.Set(valMap) // If we had errors, return those if len(errors) > 0 { return &Error{errors} } return nil } func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { typ := dataVal.Type() for i := 0; i < typ.NumField(); i++ { // Get the StructField first since this is a cheap operation. If the // field is unexported, then ignore it. f := typ.Field(i) if f.PkgPath != "" { continue } // Next get the actual value of this field and verify it is assignable // to the map value. v := dataVal.Field(i) if !v.Type().AssignableTo(valMap.Type().Elem()) { return fmt.Errorf("cannot assign type '%s' to map value field of type '%s'", v.Type(), valMap.Type().Elem()) } tagValue := f.Tag.Get(d.config.TagName) tagParts := strings.Split(tagValue, ",") // Determine the name of the key in the map keyName := f.Name if tagParts[0] != "" { if tagParts[0] == "-" { continue } keyName = tagParts[0] } // If "squash" is specified in the tag, we squash the field down. squash := false for _, tag := range tagParts[1:] { if tag == "squash" { squash = true break } } if squash && v.Kind() != reflect.Struct { return fmt.Errorf("cannot squash non-struct type '%s'", v.Type()) } switch v.Kind() { // this is an embedded struct, so handle it differently case reflect.Struct: x := reflect.New(v.Type()) x.Elem().Set(v) vType := valMap.Type() vKeyType := vType.Key() vElemType := vType.Elem() mType := reflect.MapOf(vKeyType, vElemType) vMap := reflect.MakeMap(mType) err := d.decode(keyName, x.Interface(), vMap) if err != nil { return err } if squash { for _, k := range vMap.MapKeys() { valMap.SetMapIndex(k, vMap.MapIndex(k)) } } else { valMap.SetMapIndex(reflect.ValueOf(keyName), vMap) } default: valMap.SetMapIndex(reflect.ValueOf(keyName), v) } } if val.CanAddr() { val.Set(valMap) } return nil } func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error { // If the input data is nil, then we want to just set the output // pointer to be nil as well. isNil := data == nil if !isNil { switch v := reflect.Indirect(reflect.ValueOf(data)); v.Kind() { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: isNil = v.IsNil() } } if isNil { if !val.IsNil() && val.CanSet() { nilValue := reflect.New(val.Type()).Elem() val.Set(nilValue) } return nil } // Create an element of the concrete (non pointer) type and decode // into that. Then set the value of the pointer to this type. valType := val.Type() valElemType := valType.Elem() if val.CanSet() { realVal := val if realVal.IsNil() || d.config.ZeroFields { realVal = reflect.New(valElemType) } if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil { return err } val.Set(realVal) } else { if err := d.decode(name, data, reflect.Indirect(val)); err != nil { return err } } return nil } func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) error { // Create an element of the concrete (non pointer) type and decode // into that. Then set the value of the pointer to this type. dataVal := reflect.Indirect(reflect.ValueOf(data)) if val.Type() != dataVal.Type() { return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s'", name, val.Type(), dataVal.Type()) } val.Set(dataVal) return nil } func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataValKind := dataVal.Kind() valType := val.Type() valElemType := valType.Elem() sliceType := reflect.SliceOf(valElemType) valSlice := val if valSlice.IsNil() || d.config.ZeroFields { if d.config.WeaklyTypedInput { switch { // Slice and array we use the normal logic case dataValKind == reflect.Slice, dataValKind == reflect.Array: break // Empty maps turn into empty slices case dataValKind == reflect.Map: if dataVal.Len() == 0 { val.Set(reflect.MakeSlice(sliceType, 0, 0)) return nil } // Create slice of maps of other sizes return d.decodeSlice(name, []interface{}{data}, val) case dataValKind == reflect.String && valElemType.Kind() == reflect.Uint8: return d.decodeSlice(name, []byte(dataVal.String()), val) // All other types we try to convert to the slice type // and "lift" it into it. i.e. a string becomes a string slice. default: // Just re-try this function with data as a slice. return d.decodeSlice(name, []interface{}{data}, val) } } // Check input type if dataValKind != reflect.Array && dataValKind != reflect.Slice { return fmt.Errorf( "'%s': source data must be an array or slice, got %s", name, dataValKind) } // If the input value is empty, then don't allocate since non-nil != nil if dataVal.Len() == 0 { return nil } // Make a new slice to hold our result, same size as the original data. valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len()) } // Accumulate any errors errors := make([]string, 0) for i := 0; i < dataVal.Len(); i++ { currentData := dataVal.Index(i).Interface() for valSlice.Len() <= i { valSlice = reflect.Append(valSlice, reflect.Zero(valElemType)) } currentField := valSlice.Index(i) fieldName := fmt.Sprintf("%s[%d]", name, i) if err := d.decode(fieldName, currentData, currentField); err != nil { errors = appendErrors(errors, err) } } // Finally, set the value to the slice we built up val.Set(valSlice) // If there were errors, we return those if len(errors) > 0 { return &Error{errors} } return nil } func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataValKind := dataVal.Kind() valType := val.Type() valElemType := valType.Elem() arrayType := reflect.ArrayOf(valType.Len(), valElemType) valArray := val if valArray.Interface() == reflect.Zero(valArray.Type()).Interface() || d.config.ZeroFields { // Check input type if dataValKind != reflect.Array && dataValKind != reflect.Slice { if d.config.WeaklyTypedInput { switch { // Empty maps turn into empty arrays case dataValKind == reflect.Map: if dataVal.Len() == 0 { val.Set(reflect.Zero(arrayType)) return nil } // All other types we try to convert to the array type // and "lift" it into it. i.e. a string becomes a string array. default: // Just re-try this function with data as a slice. return d.decodeArray(name, []interface{}{data}, val) } } return fmt.Errorf( "'%s': source data must be an array or slice, got %s", name, dataValKind) } if dataVal.Len() > arrayType.Len() { return fmt.Errorf( "'%s': expected source data to have length less or equal to %d, got %d", name, arrayType.Len(), dataVal.Len()) } // Make a new array to hold our result, same size as the original data. valArray = reflect.New(arrayType).Elem() } // Accumulate any errors errors := make([]string, 0) for i := 0; i < dataVal.Len(); i++ { currentData := dataVal.Index(i).Interface() currentField := valArray.Index(i) fieldName := fmt.Sprintf("%s[%d]", name, i) if err := d.decode(fieldName, currentData, currentField); err != nil { errors = appendErrors(errors, err) } } // Finally, set the value to the array we built up val.Set(valArray) // If there were errors, we return those if len(errors) > 0 { return &Error{errors} } return nil } func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) // If the type of the value to write to and the data match directly, // then we just set it directly instead of recursing into the structure. if dataVal.Type() == val.Type() { val.Set(dataVal) return nil } dataValKind := dataVal.Kind() switch dataValKind { case reflect.Map: return d.decodeStructFromMap(name, dataVal, val) case reflect.Struct: // Not the most efficient way to do this but we can optimize later if // we want to. To convert from struct to struct we go to map first // as an intermediary. m := make(map[string]interface{}) mval := reflect.Indirect(reflect.ValueOf(&m)) if err := d.decodeMapFromStruct(name, dataVal, mval, mval); err != nil { return err } result := d.decodeStructFromMap(name, mval, val) return result default: return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) } } func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error { dataValType := dataVal.Type() if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface { return fmt.Errorf( "'%s' needs a map with string keys, has '%s' keys", name, dataValType.Key().Kind()) } dataValKeys := make(map[reflect.Value]struct{}) dataValKeysUnused := make(map[interface{}]struct{}) for _, dataValKey := range dataVal.MapKeys() { dataValKeys[dataValKey] = struct{}{} dataValKeysUnused[dataValKey.Interface()] = struct{}{} } errors := make([]string, 0) // This slice will keep track of all the structs we'll be decoding. // There can be more than one struct if there are embedded structs // that are squashed. structs := make([]reflect.Value, 1, 5) structs[0] = val // Compile the list of all the fields that we're going to be decoding // from all the structs. type field struct { field reflect.StructField val reflect.Value } fields := []field{} for len(structs) > 0 { structVal := structs[0] structs = structs[1:] structType := structVal.Type() for i := 0; i < structType.NumField(); i++ { fieldType := structType.Field(i) fieldKind := fieldType.Type.Kind() // If "squash" is specified in the tag, we squash the field down. squash := false tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",") for _, tag := range tagParts[1:] { if tag == "squash" { squash = true break } } if squash { if fieldKind != reflect.Struct { errors = appendErrors(errors, fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldKind)) } else { structs = append(structs, structVal.FieldByName(fieldType.Name)) } continue } // Normal struct field, store it away fields = append(fields, field{fieldType, structVal.Field(i)}) } } // for fieldType, field := range fields { for _, f := range fields { field, fieldValue := f.field, f.val fieldName := field.Name tagValue := field.Tag.Get(d.config.TagName) tagValue = strings.SplitN(tagValue, ",", 2)[0] if tagValue != "" { fieldName = tagValue } rawMapKey := reflect.ValueOf(fieldName) rawMapVal := dataVal.MapIndex(rawMapKey) if !rawMapVal.IsValid() { // Do a slower search by iterating over each key and // doing case-insensitive search. for dataValKey := range dataValKeys { mK, ok := dataValKey.Interface().(string) if !ok { // Not a string key continue } if strings.EqualFold(mK, fieldName) { rawMapKey = dataValKey rawMapVal = dataVal.MapIndex(dataValKey) break } } if !rawMapVal.IsValid() { // There was no matching key in the map for the value in // the struct. Just ignore. continue } } // Delete the key we're using from the unused map so we stop tracking delete(dataValKeysUnused, rawMapKey.Interface()) if !fieldValue.IsValid() { // This should never happen panic("field is not valid") } // If we can't set the field, then it is unexported or something, // and we just continue onwards. if !fieldValue.CanSet() { continue } // If the name is empty string, then we're at the root, and we // don't dot-join the fields. if name != "" { fieldName = fmt.Sprintf("%s.%s", name, fieldName) } if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil { errors = appendErrors(errors, err) } } if d.config.ErrorUnused && len(dataValKeysUnused) > 0 { keys := make([]string, 0, len(dataValKeysUnused)) for rawKey := range dataValKeysUnused { keys = append(keys, rawKey.(string)) } sort.Strings(keys) err := fmt.Errorf("'%s' has invalid keys: %s", name, strings.Join(keys, ", ")) errors = appendErrors(errors, err) } if len(errors) > 0 { return &Error{errors} } // Add the unused keys to the list of unused keys if we're tracking metadata if d.config.Metadata != nil { for rawKey := range dataValKeysUnused { key := rawKey.(string) if name != "" { key = fmt.Sprintf("%s.%s", name, key) } d.config.Metadata.Unused = append(d.config.Metadata.Unused, key) } } return nil } func getKind(val reflect.Value) reflect.Kind { kind := val.Kind() switch { case kind >= reflect.Int && kind <= reflect.Int64: return reflect.Int case kind >= reflect.Uint && kind <= reflect.Uint64: return reflect.Uint case kind >= reflect.Float32 && kind <= reflect.Float64: return reflect.Float32 default: return kind } } mapstructure-1.1.2/mapstructure_benchmark_test.go000066400000000000000000000124611335556672700224360ustar00rootroot00000000000000package mapstructure import ( "encoding/json" "testing" ) func Benchmark_Decode(b *testing.B) { type Person struct { Name string Age int Emails []string Extra map[string]string } input := map[string]interface{}{ "name": "Mitchell", "age": 91, "emails": []string{"one", "two", "three"}, "extra": map[string]string{ "twitter": "mitchellh", }, } var result Person for i := 0; i < b.N; i++ { Decode(input, &result) } } // decodeViaJSON takes the map data and passes it through encoding/json to convert it into the // given Go native structure pointed to by v. v must be a pointer to a struct. func decodeViaJSON(data interface{}, v interface{}) error { // Perform the task by simply marshalling the input into JSON, // then unmarshalling it into target native Go struct. b, err := json.Marshal(data) if err != nil { return err } return json.Unmarshal(b, v) } func Benchmark_DecodeViaJSON(b *testing.B) { type Person struct { Name string Age int Emails []string Extra map[string]string } input := map[string]interface{}{ "name": "Mitchell", "age": 91, "emails": []string{"one", "two", "three"}, "extra": map[string]string{ "twitter": "mitchellh", }, } var result Person for i := 0; i < b.N; i++ { decodeViaJSON(input, &result) } } func Benchmark_DecodeBasic(b *testing.B) { input := map[string]interface{}{ "vstring": "foo", "vint": 42, "Vuint": 42, "vbool": true, "Vfloat": 42.42, "vsilent": true, "vdata": 42, } var result Basic for i := 0; i < b.N; i++ { Decode(input, &result) } } func Benchmark_DecodeEmbedded(b *testing.B) { input := map[string]interface{}{ "vstring": "foo", "Basic": map[string]interface{}{ "vstring": "innerfoo", }, "vunique": "bar", } var result Embedded for i := 0; i < b.N; i++ { Decode(input, &result) } } func Benchmark_DecodeTypeConversion(b *testing.B) { input := map[string]interface{}{ "IntToFloat": 42, "IntToUint": 42, "IntToBool": 1, "IntToString": 42, "UintToInt": 42, "UintToFloat": 42, "UintToBool": 42, "UintToString": 42, "BoolToInt": true, "BoolToUint": true, "BoolToFloat": true, "BoolToString": true, "FloatToInt": 42.42, "FloatToUint": 42.42, "FloatToBool": 42.42, "FloatToString": 42.42, "StringToInt": "42", "StringToUint": "42", "StringToBool": "1", "StringToFloat": "42.42", "SliceToMap": []interface{}{}, "MapToSlice": map[string]interface{}{}, } var resultStrict TypeConversionResult for i := 0; i < b.N; i++ { Decode(input, &resultStrict) } } func Benchmark_DecodeMap(b *testing.B) { input := map[string]interface{}{ "vfoo": "foo", "vother": map[interface{}]interface{}{ "foo": "foo", "bar": "bar", }, } var result Map for i := 0; i < b.N; i++ { Decode(input, &result) } } func Benchmark_DecodeMapOfStruct(b *testing.B) { input := map[string]interface{}{ "value": map[string]interface{}{ "foo": map[string]string{"vstring": "one"}, "bar": map[string]string{"vstring": "two"}, }, } var result MapOfStruct for i := 0; i < b.N; i++ { Decode(input, &result) } } func Benchmark_DecodeSlice(b *testing.B) { input := map[string]interface{}{ "vfoo": "foo", "vbar": []string{"foo", "bar", "baz"}, } var result Slice for i := 0; i < b.N; i++ { Decode(input, &result) } } func Benchmark_DecodeSliceOfStruct(b *testing.B) { input := map[string]interface{}{ "value": []map[string]interface{}{ {"vstring": "one"}, {"vstring": "two"}, }, } var result SliceOfStruct for i := 0; i < b.N; i++ { Decode(input, &result) } } func Benchmark_DecodeWeaklyTypedInput(b *testing.B) { type Person struct { Name string Age int Emails []string } // This input can come from anywhere, but typically comes from // something like decoding JSON, generated by a weakly typed language // such as PHP. input := map[string]interface{}{ "name": 123, // number => string "age": "42", // string => number "emails": map[string]interface{}{}, // empty map => empty array } var result Person config := &DecoderConfig{ WeaklyTypedInput: true, Result: &result, } decoder, err := NewDecoder(config) if err != nil { panic(err) } for i := 0; i < b.N; i++ { decoder.Decode(input) } } func Benchmark_DecodeMetadata(b *testing.B) { type Person struct { Name string Age int } input := map[string]interface{}{ "name": "Mitchell", "age": 91, "email": "foo@bar.com", } var md Metadata var result Person config := &DecoderConfig{ Metadata: &md, Result: &result, } decoder, err := NewDecoder(config) if err != nil { panic(err) } for i := 0; i < b.N; i++ { decoder.Decode(input) } } func Benchmark_DecodeMetadataEmbedded(b *testing.B) { input := map[string]interface{}{ "vstring": "foo", "vunique": "bar", } var md Metadata var result EmbeddedSquash config := &DecoderConfig{ Metadata: &md, Result: &result, } decoder, err := NewDecoder(config) if err != nil { b.Fatalf("err: %s", err) } for i := 0; i < b.N; i++ { decoder.Decode(input) } } func Benchmark_DecodeTagged(b *testing.B) { input := map[string]interface{}{ "foo": "bar", "bar": "value", } var result Tagged for i := 0; i < b.N; i++ { Decode(input, &result) } } mapstructure-1.1.2/mapstructure_bugs_test.go000066400000000000000000000233421335556672700214440ustar00rootroot00000000000000package mapstructure import ( "reflect" "testing" ) // GH-1, GH-10, GH-96 func TestDecode_NilValue(t *testing.T) { t.Parallel() tests := []struct { name string in interface{} target interface{} out interface{} metaKeys []string metaUnused []string }{ { "all nil", &map[string]interface{}{ "vfoo": nil, "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "", Vother: nil}, []string{"Vfoo", "Vother"}, []string{}, }, { "partial nil", &map[string]interface{}{ "vfoo": "baz", "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "baz", Vother: nil}, []string{"Vfoo", "Vother"}, []string{}, }, { "partial decode", &map[string]interface{}{ "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "foo", Vother: nil}, []string{"Vother"}, []string{}, }, { "unused values", &map[string]interface{}{ "vbar": "bar", "vfoo": nil, "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "", Vother: nil}, []string{"Vfoo", "Vother"}, []string{"vbar"}, }, { "map interface all nil", &map[interface{}]interface{}{ "vfoo": nil, "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "", Vother: nil}, []string{"Vfoo", "Vother"}, []string{}, }, { "map interface partial nil", &map[interface{}]interface{}{ "vfoo": "baz", "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "baz", Vother: nil}, []string{"Vfoo", "Vother"}, []string{}, }, { "map interface partial decode", &map[interface{}]interface{}{ "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "foo", Vother: nil}, []string{"Vother"}, []string{}, }, { "map interface unused values", &map[interface{}]interface{}{ "vbar": "bar", "vfoo": nil, "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "", Vother: nil}, []string{"Vfoo", "Vother"}, []string{"vbar"}, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { config := &DecoderConfig{ Metadata: new(Metadata), Result: tc.target, ZeroFields: true, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("should not error: %s", err) } err = decoder.Decode(tc.in) if err != nil { t.Fatalf("should not error: %s", err) } if !reflect.DeepEqual(tc.out, tc.target) { t.Fatalf("%q: TestDecode_NilValue() expected: %#v, got: %#v", tc.name, tc.out, tc.target) } if !reflect.DeepEqual(tc.metaKeys, config.Metadata.Keys) { t.Fatalf("%q: Metadata.Keys mismatch expected: %#v, got: %#v", tc.name, tc.metaKeys, config.Metadata.Keys) } if !reflect.DeepEqual(tc.metaUnused, config.Metadata.Unused) { t.Fatalf("%q: Metadata.Unused mismatch expected: %#v, got: %#v", tc.name, tc.metaUnused, config.Metadata.Unused) } }) } } // #48 func TestNestedTypePointerWithDefaults(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbar": map[string]interface{}{ "vstring": "foo", "vint": 42, "vbool": true, }, } result := NestedPointer{ Vbar: &Basic{ Vuint: 42, }, } err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vfoo != "foo" { t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) } if result.Vbar.Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring) } if result.Vbar.Vint != 42 { t.Errorf("vint value should be 42: %#v", result.Vbar.Vint) } if result.Vbar.Vbool != true { t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool) } if result.Vbar.Vextra != "" { t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra) } // this is the error if result.Vbar.Vuint != 42 { t.Errorf("vuint value should be 42: %#v", result.Vbar.Vuint) } } type NestedSlice struct { Vfoo string Vbars []Basic Vempty []Basic } // #48 func TestNestedTypeSliceWithDefaults(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbars": []map[string]interface{}{ {"vstring": "foo", "vint": 42, "vbool": true}, {"vint": 42, "vbool": true}, }, "vempty": []map[string]interface{}{ {"vstring": "foo", "vint": 42, "vbool": true}, {"vint": 42, "vbool": true}, }, } result := NestedSlice{ Vbars: []Basic{ {Vuint: 42}, {Vstring: "foo"}, }, } err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vfoo != "foo" { t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) } if result.Vbars[0].Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Vbars[0].Vstring) } // this is the error if result.Vbars[0].Vuint != 42 { t.Errorf("vuint value should be 42: %#v", result.Vbars[0].Vuint) } } // #48 workaround func TestNestedTypeWithDefaults(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbar": map[string]interface{}{ "vstring": "foo", "vint": 42, "vbool": true, }, } result := Nested{ Vbar: Basic{ Vuint: 42, }, } err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vfoo != "foo" { t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) } if result.Vbar.Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring) } if result.Vbar.Vint != 42 { t.Errorf("vint value should be 42: %#v", result.Vbar.Vint) } if result.Vbar.Vbool != true { t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool) } if result.Vbar.Vextra != "" { t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra) } // this is the error if result.Vbar.Vuint != 42 { t.Errorf("vuint value should be 42: %#v", result.Vbar.Vuint) } } // #67 panic() on extending slices (decodeSlice with disabled ZeroValues) func TestDecodeSliceToEmptySliceWOZeroing(t *testing.T) { t.Parallel() type TestStruct struct { Vfoo []string } decode := func(m interface{}, rawVal interface{}) error { config := &DecoderConfig{ Metadata: nil, Result: rawVal, ZeroFields: false, } decoder, err := NewDecoder(config) if err != nil { return err } return decoder.Decode(m) } { input := map[string]interface{}{ "vfoo": []string{"1"}, } result := &TestStruct{} err := decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } } { input := map[string]interface{}{ "vfoo": []string{"1"}, } result := &TestStruct{ Vfoo: []string{}, } err := decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } } { input := map[string]interface{}{ "vfoo": []string{"2", "3"}, } result := &TestStruct{ Vfoo: []string{"1"}, } err := decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } } } // #70 func TestNextSquashMapstructure(t *testing.T) { data := &struct { Level1 struct { Level2 struct { Foo string } `mapstructure:",squash"` } `mapstructure:",squash"` }{} err := Decode(map[interface{}]interface{}{"foo": "baz"}, &data) if err != nil { t.Fatalf("should not error: %s", err) } if data.Level1.Level2.Foo != "baz" { t.Fatal("value should be baz") } } type ImplementsInterfacePointerReceiver struct { Name string } func (i *ImplementsInterfacePointerReceiver) DoStuff() {} type ImplementsInterfaceValueReceiver string func (i ImplementsInterfaceValueReceiver) DoStuff() {} // GH-140 Type error when using DecodeHook to decode into interface func TestDecode_DecodeHookInterface(t *testing.T) { t.Parallel() type Interface interface { DoStuff() } type DecodeIntoInterface struct { Test Interface } testData := map[string]string{"test": "test"} stringToPointerInterfaceDecodeHook := func(from, to reflect.Type, data interface{}) (interface{}, error) { if from.Kind() != reflect.String { return data, nil } if to != reflect.TypeOf((*Interface)(nil)).Elem() { return data, nil } // Ensure interface is satisfied var impl Interface = &ImplementsInterfacePointerReceiver{data.(string)} return impl, nil } stringToValueInterfaceDecodeHook := func(from, to reflect.Type, data interface{}) (interface{}, error) { if from.Kind() != reflect.String { return data, nil } if to != reflect.TypeOf((*Interface)(nil)).Elem() { return data, nil } // Ensure interface is satisfied var impl Interface = ImplementsInterfaceValueReceiver(data.(string)) return impl, nil } { decodeInto := new(DecodeIntoInterface) decoder, _ := NewDecoder(&DecoderConfig{ DecodeHook: stringToPointerInterfaceDecodeHook, Result: decodeInto, }) err := decoder.Decode(testData) if err != nil { t.Fatalf("Decode returned error: %s", err) } expected := &ImplementsInterfacePointerReceiver{"test"} if !reflect.DeepEqual(decodeInto.Test, expected) { t.Fatalf("expected: %#v (%T), got: %#v (%T)", decodeInto.Test, decodeInto.Test, expected, expected) } } { decodeInto := new(DecodeIntoInterface) decoder, _ := NewDecoder(&DecoderConfig{ DecodeHook: stringToValueInterfaceDecodeHook, Result: decodeInto, }) err := decoder.Decode(testData) if err != nil { t.Fatalf("Decode returned error: %s", err) } expected := ImplementsInterfaceValueReceiver("test") if !reflect.DeepEqual(decodeInto.Test, expected) { t.Fatalf("expected: %#v (%T), got: %#v (%T)", decodeInto.Test, decodeInto.Test, expected, expected) } } } mapstructure-1.1.2/mapstructure_examples_test.go000066400000000000000000000111731335556672700223210ustar00rootroot00000000000000package mapstructure import ( "fmt" ) func ExampleDecode() { type Person struct { Name string Age int Emails []string Extra map[string]string } // This input can come from anywhere, but typically comes from // something like decoding JSON where we're not quite sure of the // struct initially. input := map[string]interface{}{ "name": "Mitchell", "age": 91, "emails": []string{"one", "two", "three"}, "extra": map[string]string{ "twitter": "mitchellh", }, } var result Person err := Decode(input, &result) if err != nil { panic(err) } fmt.Printf("%#v", result) // Output: // mapstructure.Person{Name:"Mitchell", Age:91, Emails:[]string{"one", "two", "three"}, Extra:map[string]string{"twitter":"mitchellh"}} } func ExampleDecode_errors() { type Person struct { Name string Age int Emails []string Extra map[string]string } // This input can come from anywhere, but typically comes from // something like decoding JSON where we're not quite sure of the // struct initially. input := map[string]interface{}{ "name": 123, "age": "bad value", "emails": []int{1, 2, 3}, } var result Person err := Decode(input, &result) if err == nil { panic("should have an error") } fmt.Println(err.Error()) // Output: // 5 error(s) decoding: // // * 'Age' expected type 'int', got unconvertible type 'string' // * 'Emails[0]' expected type 'string', got unconvertible type 'int' // * 'Emails[1]' expected type 'string', got unconvertible type 'int' // * 'Emails[2]' expected type 'string', got unconvertible type 'int' // * 'Name' expected type 'string', got unconvertible type 'int' } func ExampleDecode_metadata() { type Person struct { Name string Age int } // This input can come from anywhere, but typically comes from // something like decoding JSON where we're not quite sure of the // struct initially. input := map[string]interface{}{ "name": "Mitchell", "age": 91, "email": "foo@bar.com", } // For metadata, we make a more advanced DecoderConfig so we can // more finely configure the decoder that is used. In this case, we // just tell the decoder we want to track metadata. var md Metadata var result Person config := &DecoderConfig{ Metadata: &md, Result: &result, } decoder, err := NewDecoder(config) if err != nil { panic(err) } if err := decoder.Decode(input); err != nil { panic(err) } fmt.Printf("Unused keys: %#v", md.Unused) // Output: // Unused keys: []string{"email"} } func ExampleDecode_weaklyTypedInput() { type Person struct { Name string Age int Emails []string } // This input can come from anywhere, but typically comes from // something like decoding JSON, generated by a weakly typed language // such as PHP. input := map[string]interface{}{ "name": 123, // number => string "age": "42", // string => number "emails": map[string]interface{}{}, // empty map => empty array } var result Person config := &DecoderConfig{ WeaklyTypedInput: true, Result: &result, } decoder, err := NewDecoder(config) if err != nil { panic(err) } err = decoder.Decode(input) if err != nil { panic(err) } fmt.Printf("%#v", result) // Output: mapstructure.Person{Name:"123", Age:42, Emails:[]string{}} } func ExampleDecode_tags() { // Note that the mapstructure tags defined in the struct type // can indicate which fields the values are mapped to. type Person struct { Name string `mapstructure:"person_name"` Age int `mapstructure:"person_age"` } input := map[string]interface{}{ "person_name": "Mitchell", "person_age": 91, } var result Person err := Decode(input, &result) if err != nil { panic(err) } fmt.Printf("%#v", result) // Output: // mapstructure.Person{Name:"Mitchell", Age:91} } func ExampleDecode_embeddedStruct() { // Squashing multiple embedded structs is allowed using the squash tag. // This is demonstrated by creating a composite struct of multiple types // and decoding into it. In this case, a person can carry with it both // a Family and a Location, as well as their own FirstName. type Family struct { LastName string } type Location struct { City string } type Person struct { Family `mapstructure:",squash"` Location `mapstructure:",squash"` FirstName string } input := map[string]interface{}{ "FirstName": "Mitchell", "LastName": "Hashimoto", "City": "San Francisco", } var result Person err := Decode(input, &result) if err != nil { panic(err) } fmt.Printf("%s %s, %s", result.FirstName, result.LastName, result.City) // Output: // Mitchell Hashimoto, San Francisco } mapstructure-1.1.2/mapstructure_test.go000066400000000000000000001142471335556672700204310ustar00rootroot00000000000000package mapstructure import ( "encoding/json" "io" "reflect" "sort" "strings" "testing" ) type Basic struct { Vstring string Vint int Vuint uint Vbool bool Vfloat float64 Vextra string vsilent bool Vdata interface{} VjsonInt int VjsonFloat float64 VjsonNumber json.Number } type BasicPointer struct { Vstring *string Vint *int Vuint *uint Vbool *bool Vfloat *float64 Vextra *string vsilent *bool Vdata *interface{} VjsonInt *int VjsonFloat *float64 VjsonNumber *json.Number } type BasicSquash struct { Test Basic `mapstructure:",squash"` } type Embedded struct { Basic Vunique string } type EmbeddedPointer struct { *Basic Vunique string } type EmbeddedSquash struct { Basic `mapstructure:",squash"` Vunique string } type SliceAlias []string type EmbeddedSlice struct { SliceAlias `mapstructure:"slice_alias"` Vunique string } type ArrayAlias [2]string type EmbeddedArray struct { ArrayAlias `mapstructure:"array_alias"` Vunique string } type SquashOnNonStructType struct { InvalidSquashType int `mapstructure:",squash"` } type Map struct { Vfoo string Vother map[string]string } type MapOfStruct struct { Value map[string]Basic } type Nested struct { Vfoo string Vbar Basic } type NestedPointer struct { Vfoo string Vbar *Basic } type NilInterface struct { W io.Writer } type NilPointer struct { Value *string } type Slice struct { Vfoo string Vbar []string } type SliceOfAlias struct { Vfoo string Vbar SliceAlias } type SliceOfStruct struct { Value []Basic } type SlicePointer struct { Vbar *[]string } type Array struct { Vfoo string Vbar [2]string } type ArrayOfStruct struct { Value [2]Basic } type Func struct { Foo func() string } type Tagged struct { Extra string `mapstructure:"bar,what,what"` Value string `mapstructure:"foo"` } type TypeConversionResult struct { IntToFloat float32 IntToUint uint IntToBool bool IntToString string UintToInt int UintToFloat float32 UintToBool bool UintToString string BoolToInt int BoolToUint uint BoolToFloat float32 BoolToString string FloatToInt int FloatToUint uint FloatToBool bool FloatToString string SliceUint8ToString string StringToSliceUint8 []byte ArrayUint8ToString string StringToInt int StringToUint uint StringToBool bool StringToFloat float32 StringToStrSlice []string StringToIntSlice []int StringToStrArray [1]string StringToIntArray [1]int SliceToMap map[string]interface{} MapToSlice []interface{} ArrayToMap map[string]interface{} MapToArray [1]interface{} } func TestBasicTypes(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", "vint": 42, "Vuint": 42, "vbool": true, "Vfloat": 42.42, "vsilent": true, "vdata": 42, "vjsonInt": json.Number("1234"), "vjsonFloat": json.Number("1234.5"), "vjsonNumber": json.Number("1234.5"), } var result Basic err := Decode(input, &result) if err != nil { t.Errorf("got an err: %s", err.Error()) t.FailNow() } if result.Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Vstring) } if result.Vint != 42 { t.Errorf("vint value should be 42: %#v", result.Vint) } if result.Vuint != 42 { t.Errorf("vuint value should be 42: %#v", result.Vuint) } if result.Vbool != true { t.Errorf("vbool value should be true: %#v", result.Vbool) } if result.Vfloat != 42.42 { t.Errorf("vfloat value should be 42.42: %#v", result.Vfloat) } if result.Vextra != "" { t.Errorf("vextra value should be empty: %#v", result.Vextra) } if result.vsilent != false { t.Error("vsilent should not be set, it is unexported") } if result.Vdata != 42 { t.Error("vdata should be valid") } if result.VjsonInt != 1234 { t.Errorf("vjsonint value should be 1234: %#v", result.VjsonInt) } if result.VjsonFloat != 1234.5 { t.Errorf("vjsonfloat value should be 1234.5: %#v", result.VjsonFloat) } if !reflect.DeepEqual(result.VjsonNumber, json.Number("1234.5")) { t.Errorf("vjsonnumber value should be '1234.5': %T, %#v", result.VjsonNumber, result.VjsonNumber) } } func TestBasic_IntWithFloat(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vint": float64(42), } var result Basic err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err) } } func TestBasic_Merge(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vint": 42, } var result Basic result.Vuint = 100 err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err) } expected := Basic{ Vint: 42, Vuint: 100, } if !reflect.DeepEqual(result, expected) { t.Fatalf("bad: %#v", result) } } // Test for issue #46. func TestBasic_Struct(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vdata": map[string]interface{}{ "vstring": "foo", }, } var result, inner Basic result.Vdata = &inner err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err) } expected := Basic{ Vdata: &Basic{ Vstring: "foo", }, } if !reflect.DeepEqual(result, expected) { t.Fatalf("bad: %#v", result) } } func TestDecode_BasicSquash(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", } var result BasicSquash err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Test.Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Test.Vstring) } } func TestDecodeFrom_BasicSquash(t *testing.T) { t.Parallel() var v interface{} var ok bool input := BasicSquash{ Test: Basic{ Vstring: "foo", }, } var result map[string]interface{} err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if _, ok = result["Test"]; ok { t.Error("test should not be present in map") } v, ok = result["Vstring"] if !ok { t.Error("vstring should be present in map") } else if !reflect.DeepEqual(v, "foo") { t.Errorf("vstring value should be 'foo': %#v", v) } } func TestDecode_Embedded(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", "Basic": map[string]interface{}{ "vstring": "innerfoo", }, "vunique": "bar", } var result Embedded err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vstring != "innerfoo" { t.Errorf("vstring value should be 'innerfoo': %#v", result.Vstring) } if result.Vunique != "bar" { t.Errorf("vunique value should be 'bar': %#v", result.Vunique) } } func TestDecode_EmbeddedPointer(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", "Basic": map[string]interface{}{ "vstring": "innerfoo", }, "vunique": "bar", } var result EmbeddedPointer err := Decode(input, &result) if err != nil { t.Fatalf("err: %s", err) } expected := EmbeddedPointer{ Basic: &Basic{ Vstring: "innerfoo", }, Vunique: "bar", } if !reflect.DeepEqual(result, expected) { t.Fatalf("bad: %#v", result) } } func TestDecode_EmbeddedSlice(t *testing.T) { t.Parallel() input := map[string]interface{}{ "slice_alias": []string{"foo", "bar"}, "vunique": "bar", } var result EmbeddedSlice err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if !reflect.DeepEqual(result.SliceAlias, SliceAlias([]string{"foo", "bar"})) { t.Errorf("slice value: %#v", result.SliceAlias) } if result.Vunique != "bar" { t.Errorf("vunique value should be 'bar': %#v", result.Vunique) } } func TestDecode_EmbeddedArray(t *testing.T) { t.Parallel() input := map[string]interface{}{ "array_alias": [2]string{"foo", "bar"}, "vunique": "bar", } var result EmbeddedArray err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if !reflect.DeepEqual(result.ArrayAlias, ArrayAlias([2]string{"foo", "bar"})) { t.Errorf("array value: %#v", result.ArrayAlias) } if result.Vunique != "bar" { t.Errorf("vunique value should be 'bar': %#v", result.Vunique) } } func TestDecode_EmbeddedSquash(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", "vunique": "bar", } var result EmbeddedSquash err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Vstring) } if result.Vunique != "bar" { t.Errorf("vunique value should be 'bar': %#v", result.Vunique) } } func TestDecodeFrom_EmbeddedSquash(t *testing.T) { t.Parallel() var v interface{} var ok bool input := EmbeddedSquash{ Basic: Basic{ Vstring: "foo", }, Vunique: "bar", } var result map[string]interface{} err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if _, ok = result["Basic"]; ok { t.Error("basic should not be present in map") } v, ok = result["Vstring"] if !ok { t.Error("vstring should be present in map") } else if !reflect.DeepEqual(v, "foo") { t.Errorf("vstring value should be 'foo': %#v", v) } v, ok = result["Vunique"] if !ok { t.Error("vunique should be present in map") } else if !reflect.DeepEqual(v, "bar") { t.Errorf("vunique value should be 'bar': %#v", v) } } func TestDecode_SquashOnNonStructType(t *testing.T) { t.Parallel() input := map[string]interface{}{ "InvalidSquashType": 42, } var result SquashOnNonStructType err := Decode(input, &result) if err == nil { t.Fatal("unexpected success decoding invalid squash field type") } else if !strings.Contains(err.Error(), "unsupported type for squash") { t.Fatalf("unexpected error message for invalid squash field type: %s", err) } } func TestDecode_DecodeHook(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vint": "WHAT", } decodeHook := func(from reflect.Kind, to reflect.Kind, v interface{}) (interface{}, error) { if from == reflect.String && to != reflect.String { return 5, nil } return v, nil } var result Basic config := &DecoderConfig{ DecodeHook: decodeHook, Result: &result, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err != nil { t.Fatalf("got an err: %s", err) } if result.Vint != 5 { t.Errorf("vint should be 5: %#v", result.Vint) } } func TestDecode_DecodeHookType(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vint": "WHAT", } decodeHook := func(from reflect.Type, to reflect.Type, v interface{}) (interface{}, error) { if from.Kind() == reflect.String && to.Kind() != reflect.String { return 5, nil } return v, nil } var result Basic config := &DecoderConfig{ DecodeHook: decodeHook, Result: &result, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err != nil { t.Fatalf("got an err: %s", err) } if result.Vint != 5 { t.Errorf("vint should be 5: %#v", result.Vint) } } func TestDecode_Nil(t *testing.T) { t.Parallel() var input interface{} result := Basic{ Vstring: "foo", } err := Decode(input, &result) if err != nil { t.Fatalf("err: %s", err) } if result.Vstring != "foo" { t.Fatalf("bad: %#v", result.Vstring) } } func TestDecode_NilInterfaceHook(t *testing.T) { t.Parallel() input := map[string]interface{}{ "w": "", } decodeHook := func(f, t reflect.Type, v interface{}) (interface{}, error) { if t.String() == "io.Writer" { return nil, nil } return v, nil } var result NilInterface config := &DecoderConfig{ DecodeHook: decodeHook, Result: &result, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err != nil { t.Fatalf("got an err: %s", err) } if result.W != nil { t.Errorf("W should be nil: %#v", result.W) } } func TestDecode_NilPointerHook(t *testing.T) { t.Parallel() input := map[string]interface{}{ "value": "", } decodeHook := func(f, t reflect.Type, v interface{}) (interface{}, error) { if typed, ok := v.(string); ok { if typed == "" { return nil, nil } } return v, nil } var result NilPointer config := &DecoderConfig{ DecodeHook: decodeHook, Result: &result, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err != nil { t.Fatalf("got an err: %s", err) } if result.Value != nil { t.Errorf("W should be nil: %#v", result.Value) } } func TestDecode_FuncHook(t *testing.T) { t.Parallel() input := map[string]interface{}{ "foo": "baz", } decodeHook := func(f, t reflect.Type, v interface{}) (interface{}, error) { if t.Kind() != reflect.Func { return v, nil } val := v.(string) return func() string { return val }, nil } var result Func config := &DecoderConfig{ DecodeHook: decodeHook, Result: &result, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err != nil { t.Fatalf("got an err: %s", err) } if result.Foo() != "baz" { t.Errorf("Foo call result should be 'baz': %s", result.Foo()) } } func TestDecode_NonStruct(t *testing.T) { t.Parallel() input := map[string]interface{}{ "foo": "bar", "bar": "baz", } var result map[string]string err := Decode(input, &result) if err != nil { t.Fatalf("err: %s", err) } if result["foo"] != "bar" { t.Fatal("foo is not bar") } } func TestDecode_StructMatch(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vbar": Basic{ Vstring: "foo", }, } var result Nested err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vbar.Vstring != "foo" { t.Errorf("bad: %#v", result) } } func TestDecode_TypeConversion(t *testing.T) { input := map[string]interface{}{ "IntToFloat": 42, "IntToUint": 42, "IntToBool": 1, "IntToString": 42, "UintToInt": 42, "UintToFloat": 42, "UintToBool": 42, "UintToString": 42, "BoolToInt": true, "BoolToUint": true, "BoolToFloat": true, "BoolToString": true, "FloatToInt": 42.42, "FloatToUint": 42.42, "FloatToBool": 42.42, "FloatToString": 42.42, "SliceUint8ToString": []uint8("foo"), "StringToSliceUint8": "foo", "ArrayUint8ToString": [3]uint8{'f', 'o', 'o'}, "StringToInt": "42", "StringToUint": "42", "StringToBool": "1", "StringToFloat": "42.42", "StringToStrSlice": "A", "StringToIntSlice": "42", "StringToStrArray": "A", "StringToIntArray": "42", "SliceToMap": []interface{}{}, "MapToSlice": map[string]interface{}{}, "ArrayToMap": []interface{}{}, "MapToArray": map[string]interface{}{}, } expectedResultStrict := TypeConversionResult{ IntToFloat: 42.0, IntToUint: 42, UintToInt: 42, UintToFloat: 42, BoolToInt: 0, BoolToUint: 0, BoolToFloat: 0, FloatToInt: 42, FloatToUint: 42, } expectedResultWeak := TypeConversionResult{ IntToFloat: 42.0, IntToUint: 42, IntToBool: true, IntToString: "42", UintToInt: 42, UintToFloat: 42, UintToBool: true, UintToString: "42", BoolToInt: 1, BoolToUint: 1, BoolToFloat: 1, BoolToString: "1", FloatToInt: 42, FloatToUint: 42, FloatToBool: true, FloatToString: "42.42", SliceUint8ToString: "foo", StringToSliceUint8: []byte("foo"), ArrayUint8ToString: "foo", StringToInt: 42, StringToUint: 42, StringToBool: true, StringToFloat: 42.42, StringToStrSlice: []string{"A"}, StringToIntSlice: []int{42}, StringToStrArray: [1]string{"A"}, StringToIntArray: [1]int{42}, SliceToMap: map[string]interface{}{}, MapToSlice: []interface{}{}, ArrayToMap: map[string]interface{}{}, MapToArray: [1]interface{}{}, } // Test strict type conversion var resultStrict TypeConversionResult err := Decode(input, &resultStrict) if err == nil { t.Errorf("should return an error") } if !reflect.DeepEqual(resultStrict, expectedResultStrict) { t.Errorf("expected %v, got: %v", expectedResultStrict, resultStrict) } // Test weak type conversion var decoder *Decoder var resultWeak TypeConversionResult config := &DecoderConfig{ WeaklyTypedInput: true, Result: &resultWeak, } decoder, err = NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err != nil { t.Fatalf("got an err: %s", err) } if !reflect.DeepEqual(resultWeak, expectedResultWeak) { t.Errorf("expected \n%#v, got: \n%#v", expectedResultWeak, resultWeak) } } func TestDecoder_ErrorUnused(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "hello", "foo": "bar", } var result Basic config := &DecoderConfig{ ErrorUnused: true, Result: &result, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err == nil { t.Fatal("expected error") } } func TestMap(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vother": map[interface{}]interface{}{ "foo": "foo", "bar": "bar", }, } var result Map err := Decode(input, &result) if err != nil { t.Fatalf("got an error: %s", err) } if result.Vfoo != "foo" { t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) } if result.Vother == nil { t.Fatal("vother should not be nil") } if len(result.Vother) != 2 { t.Error("vother should have two items") } if result.Vother["foo"] != "foo" { t.Errorf("'foo' key should be foo, got: %#v", result.Vother["foo"]) } if result.Vother["bar"] != "bar" { t.Errorf("'bar' key should be bar, got: %#v", result.Vother["bar"]) } } func TestMapMerge(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vother": map[interface{}]interface{}{ "foo": "foo", "bar": "bar", }, } var result Map result.Vother = map[string]string{"hello": "world"} err := Decode(input, &result) if err != nil { t.Fatalf("got an error: %s", err) } if result.Vfoo != "foo" { t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) } expected := map[string]string{ "foo": "foo", "bar": "bar", "hello": "world", } if !reflect.DeepEqual(result.Vother, expected) { t.Errorf("bad: %#v", result.Vother) } } func TestMapOfStruct(t *testing.T) { t.Parallel() input := map[string]interface{}{ "value": map[string]interface{}{ "foo": map[string]string{"vstring": "one"}, "bar": map[string]string{"vstring": "two"}, }, } var result MapOfStruct err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err) } if result.Value == nil { t.Fatal("value should not be nil") } if len(result.Value) != 2 { t.Error("value should have two items") } if result.Value["foo"].Vstring != "one" { t.Errorf("foo value should be 'one', got: %s", result.Value["foo"].Vstring) } if result.Value["bar"].Vstring != "two" { t.Errorf("bar value should be 'two', got: %s", result.Value["bar"].Vstring) } } func TestNestedType(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbar": map[string]interface{}{ "vstring": "foo", "vint": 42, "vbool": true, }, } var result Nested err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vfoo != "foo" { t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) } if result.Vbar.Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring) } if result.Vbar.Vint != 42 { t.Errorf("vint value should be 42: %#v", result.Vbar.Vint) } if result.Vbar.Vbool != true { t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool) } if result.Vbar.Vextra != "" { t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra) } } func TestNestedTypePointer(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbar": &map[string]interface{}{ "vstring": "foo", "vint": 42, "vbool": true, }, } var result NestedPointer err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vfoo != "foo" { t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) } if result.Vbar.Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring) } if result.Vbar.Vint != 42 { t.Errorf("vint value should be 42: %#v", result.Vbar.Vint) } if result.Vbar.Vbool != true { t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool) } if result.Vbar.Vextra != "" { t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra) } } // Test for issue #46. func TestNestedTypeInterface(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbar": &map[string]interface{}{ "vstring": "foo", "vint": 42, "vbool": true, "vdata": map[string]interface{}{ "vstring": "bar", }, }, } var result NestedPointer result.Vbar = new(Basic) result.Vbar.Vdata = new(Basic) err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vfoo != "foo" { t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) } if result.Vbar.Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring) } if result.Vbar.Vint != 42 { t.Errorf("vint value should be 42: %#v", result.Vbar.Vint) } if result.Vbar.Vbool != true { t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool) } if result.Vbar.Vextra != "" { t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra) } if result.Vbar.Vdata.(*Basic).Vstring != "bar" { t.Errorf("vstring value should be 'bar': %#v", result.Vbar.Vdata.(*Basic).Vstring) } } func TestSlice(t *testing.T) { t.Parallel() inputStringSlice := map[string]interface{}{ "vfoo": "foo", "vbar": []string{"foo", "bar", "baz"}, } inputStringSlicePointer := map[string]interface{}{ "vfoo": "foo", "vbar": &[]string{"foo", "bar", "baz"}, } outputStringSlice := &Slice{ "foo", []string{"foo", "bar", "baz"}, } testSliceInput(t, inputStringSlice, outputStringSlice) testSliceInput(t, inputStringSlicePointer, outputStringSlice) } func TestInvalidSlice(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbar": 42, } result := Slice{} err := Decode(input, &result) if err == nil { t.Errorf("expected failure") } } func TestSliceOfStruct(t *testing.T) { t.Parallel() input := map[string]interface{}{ "value": []map[string]interface{}{ {"vstring": "one"}, {"vstring": "two"}, }, } var result SliceOfStruct err := Decode(input, &result) if err != nil { t.Fatalf("got unexpected error: %s", err) } if len(result.Value) != 2 { t.Fatalf("expected two values, got %d", len(result.Value)) } if result.Value[0].Vstring != "one" { t.Errorf("first value should be 'one', got: %s", result.Value[0].Vstring) } if result.Value[1].Vstring != "two" { t.Errorf("second value should be 'two', got: %s", result.Value[1].Vstring) } } func TestSliceCornerCases(t *testing.T) { t.Parallel() // Input with a map with zero values input := map[string]interface{}{} var resultWeak []Basic err := WeakDecode(input, &resultWeak) if err != nil { t.Fatalf("got unexpected error: %s", err) } if len(resultWeak) != 0 { t.Errorf("length should be 0") } // Input with more values input = map[string]interface{}{ "Vstring": "foo", } resultWeak = nil err = WeakDecode(input, &resultWeak) if err != nil { t.Fatalf("got unexpected error: %s", err) } if resultWeak[0].Vstring != "foo" { t.Errorf("value does not match") } } func TestSliceToMap(t *testing.T) { t.Parallel() input := []map[string]interface{}{ { "foo": "bar", }, { "bar": "baz", }, } var result map[string]interface{} err := WeakDecode(input, &result) if err != nil { t.Fatalf("got an error: %s", err) } expected := map[string]interface{}{ "foo": "bar", "bar": "baz", } if !reflect.DeepEqual(result, expected) { t.Errorf("bad: %#v", result) } } func TestArray(t *testing.T) { t.Parallel() inputStringArray := map[string]interface{}{ "vfoo": "foo", "vbar": [2]string{"foo", "bar"}, } inputStringArrayPointer := map[string]interface{}{ "vfoo": "foo", "vbar": &[2]string{"foo", "bar"}, } outputStringArray := &Array{ "foo", [2]string{"foo", "bar"}, } testArrayInput(t, inputStringArray, outputStringArray) testArrayInput(t, inputStringArrayPointer, outputStringArray) } func TestInvalidArray(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbar": 42, } result := Array{} err := Decode(input, &result) if err == nil { t.Errorf("expected failure") } } func TestArrayOfStruct(t *testing.T) { t.Parallel() input := map[string]interface{}{ "value": []map[string]interface{}{ {"vstring": "one"}, {"vstring": "two"}, }, } var result ArrayOfStruct err := Decode(input, &result) if err != nil { t.Fatalf("got unexpected error: %s", err) } if len(result.Value) != 2 { t.Fatalf("expected two values, got %d", len(result.Value)) } if result.Value[0].Vstring != "one" { t.Errorf("first value should be 'one', got: %s", result.Value[0].Vstring) } if result.Value[1].Vstring != "two" { t.Errorf("second value should be 'two', got: %s", result.Value[1].Vstring) } } func TestArrayToMap(t *testing.T) { t.Parallel() input := []map[string]interface{}{ { "foo": "bar", }, { "bar": "baz", }, } var result map[string]interface{} err := WeakDecode(input, &result) if err != nil { t.Fatalf("got an error: %s", err) } expected := map[string]interface{}{ "foo": "bar", "bar": "baz", } if !reflect.DeepEqual(result, expected) { t.Errorf("bad: %#v", result) } } func TestDecodeTable(t *testing.T) { t.Parallel() // We need to make new types so that we don't get the short-circuit // copy functionality. We want to test the deep copying functionality. type BasicCopy Basic type NestedPointerCopy NestedPointer type MapCopy Map tests := []struct { name string in interface{} target interface{} out interface{} wantErr bool }{ { "basic struct input", &Basic{ Vstring: "vstring", Vint: 2, Vuint: 3, Vbool: true, Vfloat: 4.56, Vextra: "vextra", vsilent: true, Vdata: []byte("data"), }, &map[string]interface{}{}, &map[string]interface{}{ "Vstring": "vstring", "Vint": 2, "Vuint": uint(3), "Vbool": true, "Vfloat": 4.56, "Vextra": "vextra", "Vdata": []byte("data"), "VjsonInt": 0, "VjsonFloat": 0.0, "VjsonNumber": json.Number(""), }, false, }, { "embedded struct input", &Embedded{ Vunique: "vunique", Basic: Basic{ Vstring: "vstring", Vint: 2, Vuint: 3, Vbool: true, Vfloat: 4.56, Vextra: "vextra", vsilent: true, Vdata: []byte("data"), }, }, &map[string]interface{}{}, &map[string]interface{}{ "Vunique": "vunique", "Basic": map[string]interface{}{ "Vstring": "vstring", "Vint": 2, "Vuint": uint(3), "Vbool": true, "Vfloat": 4.56, "Vextra": "vextra", "Vdata": []byte("data"), "VjsonInt": 0, "VjsonFloat": 0.0, "VjsonNumber": json.Number(""), }, }, false, }, { "struct => struct", &Basic{ Vstring: "vstring", Vint: 2, Vuint: 3, Vbool: true, Vfloat: 4.56, Vextra: "vextra", Vdata: []byte("data"), vsilent: true, }, &BasicCopy{}, &BasicCopy{ Vstring: "vstring", Vint: 2, Vuint: 3, Vbool: true, Vfloat: 4.56, Vextra: "vextra", Vdata: []byte("data"), }, false, }, { "struct => struct with pointers", &NestedPointer{ Vfoo: "hello", Vbar: nil, }, &NestedPointerCopy{}, &NestedPointerCopy{ Vfoo: "hello", }, false, }, { "basic pointer to non-pointer", &BasicPointer{ Vstring: stringPtr("vstring"), Vint: intPtr(2), Vuint: uintPtr(3), Vbool: boolPtr(true), Vfloat: floatPtr(4.56), Vdata: interfacePtr([]byte("data")), }, &Basic{}, &Basic{ Vstring: "vstring", Vint: 2, Vuint: 3, Vbool: true, Vfloat: 4.56, Vdata: []byte("data"), }, false, }, { "slice non-pointer to pointer", &Slice{}, &SlicePointer{}, &SlicePointer{}, false, }, { "slice non-pointer to pointer, zero field", &Slice{}, &SlicePointer{ Vbar: &[]string{"yo"}, }, &SlicePointer{}, false, }, { "slice to slice alias", &Slice{}, &SliceOfAlias{}, &SliceOfAlias{}, false, }, { "nil map to map", &Map{}, &MapCopy{}, &MapCopy{}, false, }, { "nil map to non-empty map", &Map{}, &MapCopy{Vother: map[string]string{"foo": "bar"}}, &MapCopy{}, false, }, { "slice input - should error", []string{"foo", "bar"}, &map[string]interface{}{}, &map[string]interface{}{}, true, }, { "struct with slice property", &Slice{ Vfoo: "vfoo", Vbar: []string{"foo", "bar"}, }, &map[string]interface{}{}, &map[string]interface{}{ "Vfoo": "vfoo", "Vbar": []string{"foo", "bar"}, }, false, }, { "struct with slice of struct property", &SliceOfStruct{ Value: []Basic{ Basic{ Vstring: "vstring", Vint: 2, Vuint: 3, Vbool: true, Vfloat: 4.56, Vextra: "vextra", vsilent: true, Vdata: []byte("data"), }, }, }, &map[string]interface{}{}, &map[string]interface{}{ "Value": []Basic{ Basic{ Vstring: "vstring", Vint: 2, Vuint: 3, Vbool: true, Vfloat: 4.56, Vextra: "vextra", vsilent: true, Vdata: []byte("data"), }, }, }, false, }, { "struct with map property", &Map{ Vfoo: "vfoo", Vother: map[string]string{"vother": "vother"}, }, &map[string]interface{}{}, &map[string]interface{}{ "Vfoo": "vfoo", "Vother": map[string]string{ "vother": "vother", }}, false, }, { "tagged struct", &Tagged{ Extra: "extra", Value: "value", }, &map[string]string{}, &map[string]string{ "bar": "extra", "foo": "value", }, false, }, { "omit tag struct", &struct { Value string `mapstructure:"value"` Omit string `mapstructure:"-"` }{ Value: "value", Omit: "omit", }, &map[string]string{}, &map[string]string{ "value": "value", }, false, }, { "decode to wrong map type", &struct { Value string }{ Value: "string", }, &map[string]int{}, &map[string]int{}, true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := Decode(tt.in, tt.target); (err != nil) != tt.wantErr { t.Fatalf("%q: TestMapOutputForStructuredInputs() unexpected error: %s", tt.name, err) } if !reflect.DeepEqual(tt.out, tt.target) { t.Fatalf("%q: TestMapOutputForStructuredInputs() expected: %#v, got: %#v", tt.name, tt.out, tt.target) } }) } } func TestInvalidType(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": 42, } var result Basic err := Decode(input, &result) if err == nil { t.Fatal("error should exist") } derr, ok := err.(*Error) if !ok { t.Fatalf("error should be kind of Error, instead: %#v", err) } if derr.Errors[0] != "'Vstring' expected type 'string', got unconvertible type 'int'" { t.Errorf("got unexpected error: %s", err) } inputNegIntUint := map[string]interface{}{ "vuint": -42, } err = Decode(inputNegIntUint, &result) if err == nil { t.Fatal("error should exist") } derr, ok = err.(*Error) if !ok { t.Fatalf("error should be kind of Error, instead: %#v", err) } if derr.Errors[0] != "cannot parse 'Vuint', -42 overflows uint" { t.Errorf("got unexpected error: %s", err) } inputNegFloatUint := map[string]interface{}{ "vuint": -42.0, } err = Decode(inputNegFloatUint, &result) if err == nil { t.Fatal("error should exist") } derr, ok = err.(*Error) if !ok { t.Fatalf("error should be kind of Error, instead: %#v", err) } if derr.Errors[0] != "cannot parse 'Vuint', -42.000000 overflows uint" { t.Errorf("got unexpected error: %s", err) } } func TestDecodeMetadata(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbar": map[string]interface{}{ "vstring": "foo", "Vuint": 42, "foo": "bar", }, "bar": "nil", } var md Metadata var result Nested err := DecodeMetadata(input, &result, &md) if err != nil { t.Fatalf("err: %s", err.Error()) } expectedKeys := []string{"Vbar", "Vbar.Vstring", "Vbar.Vuint", "Vfoo"} sort.Strings(md.Keys) if !reflect.DeepEqual(md.Keys, expectedKeys) { t.Fatalf("bad keys: %#v", md.Keys) } expectedUnused := []string{"Vbar.foo", "bar"} if !reflect.DeepEqual(md.Unused, expectedUnused) { t.Fatalf("bad unused: %#v", md.Unused) } } func TestMetadata(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbar": map[string]interface{}{ "vstring": "foo", "Vuint": 42, "foo": "bar", }, "bar": "nil", } var md Metadata var result Nested config := &DecoderConfig{ Metadata: &md, Result: &result, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err != nil { t.Fatalf("err: %s", err.Error()) } expectedKeys := []string{"Vbar", "Vbar.Vstring", "Vbar.Vuint", "Vfoo"} sort.Strings(md.Keys) if !reflect.DeepEqual(md.Keys, expectedKeys) { t.Fatalf("bad keys: %#v", md.Keys) } expectedUnused := []string{"Vbar.foo", "bar"} if !reflect.DeepEqual(md.Unused, expectedUnused) { t.Fatalf("bad unused: %#v", md.Unused) } } func TestMetadata_Embedded(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", "vunique": "bar", } var md Metadata var result EmbeddedSquash config := &DecoderConfig{ Metadata: &md, Result: &result, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err != nil { t.Fatalf("err: %s", err.Error()) } expectedKeys := []string{"Vstring", "Vunique"} sort.Strings(md.Keys) if !reflect.DeepEqual(md.Keys, expectedKeys) { t.Fatalf("bad keys: %#v", md.Keys) } expectedUnused := []string{} if !reflect.DeepEqual(md.Unused, expectedUnused) { t.Fatalf("bad unused: %#v", md.Unused) } } func TestNonPtrValue(t *testing.T) { t.Parallel() err := Decode(map[string]interface{}{}, Basic{}) if err == nil { t.Fatal("error should exist") } if err.Error() != "result must be a pointer" { t.Errorf("got unexpected error: %s", err) } } func TestTagged(t *testing.T) { t.Parallel() input := map[string]interface{}{ "foo": "bar", "bar": "value", } var result Tagged err := Decode(input, &result) if err != nil { t.Fatalf("unexpected error: %s", err) } if result.Value != "bar" { t.Errorf("value should be 'bar', got: %#v", result.Value) } if result.Extra != "value" { t.Errorf("extra should be 'value', got: %#v", result.Extra) } } func TestWeakDecode(t *testing.T) { t.Parallel() input := map[string]interface{}{ "foo": "4", "bar": "value", } var result struct { Foo int Bar string } if err := WeakDecode(input, &result); err != nil { t.Fatalf("err: %s", err) } if result.Foo != 4 { t.Fatalf("bad: %#v", result) } if result.Bar != "value" { t.Fatalf("bad: %#v", result) } } func TestWeakDecodeMetadata(t *testing.T) { t.Parallel() input := map[string]interface{}{ "foo": "4", "bar": "value", "unused": "value", } var md Metadata var result struct { Foo int Bar string } if err := WeakDecodeMetadata(input, &result, &md); err != nil { t.Fatalf("err: %s", err) } if result.Foo != 4 { t.Fatalf("bad: %#v", result) } if result.Bar != "value" { t.Fatalf("bad: %#v", result) } expectedKeys := []string{"Bar", "Foo"} sort.Strings(md.Keys) if !reflect.DeepEqual(md.Keys, expectedKeys) { t.Fatalf("bad keys: %#v", md.Keys) } expectedUnused := []string{"unused"} if !reflect.DeepEqual(md.Unused, expectedUnused) { t.Fatalf("bad unused: %#v", md.Unused) } } func testSliceInput(t *testing.T, input map[string]interface{}, expected *Slice) { var result Slice err := Decode(input, &result) if err != nil { t.Fatalf("got error: %s", err) } if result.Vfoo != expected.Vfoo { t.Errorf("Vfoo expected '%s', got '%s'", expected.Vfoo, result.Vfoo) } if result.Vbar == nil { t.Fatalf("Vbar a slice, got '%#v'", result.Vbar) } if len(result.Vbar) != len(expected.Vbar) { t.Errorf("Vbar length should be %d, got %d", len(expected.Vbar), len(result.Vbar)) } for i, v := range result.Vbar { if v != expected.Vbar[i] { t.Errorf( "Vbar[%d] should be '%#v', got '%#v'", i, expected.Vbar[i], v) } } } func testArrayInput(t *testing.T, input map[string]interface{}, expected *Array) { var result Array err := Decode(input, &result) if err != nil { t.Fatalf("got error: %s", err) } if result.Vfoo != expected.Vfoo { t.Errorf("Vfoo expected '%s', got '%s'", expected.Vfoo, result.Vfoo) } if result.Vbar == [2]string{} { t.Fatalf("Vbar a slice, got '%#v'", result.Vbar) } if len(result.Vbar) != len(expected.Vbar) { t.Errorf("Vbar length should be %d, got %d", len(expected.Vbar), len(result.Vbar)) } for i, v := range result.Vbar { if v != expected.Vbar[i] { t.Errorf( "Vbar[%d] should be '%#v', got '%#v'", i, expected.Vbar[i], v) } } } func stringPtr(v string) *string { return &v } func intPtr(v int) *int { return &v } func uintPtr(v uint) *uint { return &v } func boolPtr(v bool) *bool { return &v } func floatPtr(v float64) *float64 { return &v } func interfacePtr(v interface{}) *interface{} { return &v }