Add mitchellh/mapstructure to Godeps
Signed-off-by: Sylvain Baubeau <sbaubeau@redhat.com>
This commit is contained in:
parent
c4d845cb6c
commit
03188fe394
11 changed files with 2697 additions and 0 deletions
7
Godeps/_workspace/src/github.com/mitchellh/mapstructure/.travis.yml
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/mitchellh/mapstructure/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.4
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test
|
21
Godeps/_workspace/src/github.com/mitchellh/mapstructure/LICENSE
generated
vendored
Normal file
21
Godeps/_workspace/src/github.com/mitchellh/mapstructure/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The 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.
|
46
Godeps/_workspace/src/github.com/mitchellh/mapstructure/README.md
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/mitchellh/mapstructure/README.md
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# 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.
|
151
Godeps/_workspace/src/github.com/mitchellh/mapstructure/decode_hooks.go
generated
vendored
Normal file
151
Godeps/_workspace/src/github.com/mitchellh/mapstructure/decode_hooks.go
generated
vendored
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
package mapstructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"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) {
|
||||||
|
// Build our arguments that reflect expects
|
||||||
|
argVals := make([]reflect.Value, 3)
|
||||||
|
argVals[0] = reflect.ValueOf(from)
|
||||||
|
argVals[1] = reflect.ValueOf(to)
|
||||||
|
argVals[2] = reflect.ValueOf(data)
|
||||||
|
|
||||||
|
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 = reflect.ValueOf(data).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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
}
|
229
Godeps/_workspace/src/github.com/mitchellh/mapstructure/decode_hooks_test.go
generated
vendored
Normal file
229
Godeps/_workspace/src/github.com/mitchellh/mapstructure/decode_hooks_test.go
generated
vendored
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
package mapstructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"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 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
Godeps/_workspace/src/github.com/mitchellh/mapstructure/error.go
generated
vendored
Normal file
50
Godeps/_workspace/src/github.com/mitchellh/mapstructure/error.go
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package 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())
|
||||||
|
}
|
||||||
|
}
|
746
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure.go
generated
vendored
Normal file
746
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure.go
generated
vendored
Normal file
|
@ -0,0 +1,746 @@
|
||||||
|
// The mapstructure package exposes functionality to convert an
|
||||||
|
// abitrary 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 (
|
||||||
|
"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{}
|
||||||
|
|
||||||
|
type DecodeHookFuncType func(reflect.Type, reflect.Type, interface{}) (interface{}, error)
|
||||||
|
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)
|
||||||
|
//
|
||||||
|
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 a map and uses reflection to convert it into the
|
||||||
|
// given Go native structure. val must be a pointer to a struct.
|
||||||
|
func Decode(m interface{}, rawVal interface{}) error {
|
||||||
|
config := &DecoderConfig{
|
||||||
|
Metadata: nil,
|
||||||
|
Result: rawVal,
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder, err := NewDecoder(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoder.Decode(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(raw interface{}) error {
|
||||||
|
return d.decode("", raw, reflect.ValueOf(d.config.Result).Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes an unknown data type into a specific reflection value.
|
||||||
|
func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error {
|
||||||
|
if data == nil {
|
||||||
|
// If the data is nil, then we don't set anything.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dataVal := reflect.ValueOf(data)
|
||||||
|
if !dataVal.IsValid() {
|
||||||
|
// If the data value is invalid, then we just set the value
|
||||||
|
// to be the zero value.
|
||||||
|
val.Set(reflect.Zero(val.Type()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.config.DecodeHook != nil {
|
||||||
|
// We have a DecodeHook, so let's pre-process the data.
|
||||||
|
var err error
|
||||||
|
data, err = DecodeHookExec(
|
||||||
|
d.config.DecodeHook,
|
||||||
|
dataVal.Type(), val.Type(), data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
dataKind := getKind(val)
|
||||||
|
switch dataKind {
|
||||||
|
case reflect.Bool:
|
||||||
|
err = d.decodeBool(name, data, val)
|
||||||
|
case reflect.Interface:
|
||||||
|
err = d.decodeBasic(name, data, val)
|
||||||
|
case reflect.String:
|
||||||
|
err = d.decodeString(name, data, val)
|
||||||
|
case reflect.Int:
|
||||||
|
err = d.decodeInt(name, data, val)
|
||||||
|
case reflect.Uint:
|
||||||
|
err = d.decodeUint(name, data, val)
|
||||||
|
case reflect.Float32:
|
||||||
|
err = d.decodeFloat(name, data, val)
|
||||||
|
case reflect.Struct:
|
||||||
|
err = d.decodeStruct(name, data, val)
|
||||||
|
case reflect.Map:
|
||||||
|
err = d.decodeMap(name, data, val)
|
||||||
|
case reflect.Ptr:
|
||||||
|
err = d.decodePtr(name, data, val)
|
||||||
|
case reflect.Slice:
|
||||||
|
err = d.decodeSlice(name, data, val)
|
||||||
|
default:
|
||||||
|
// If we reached this point then we weren't able to decode it
|
||||||
|
return fmt.Errorf("%s: unsupported type: %s", name, dataKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we reached here, then we successfully decoded SOMETHING, so
|
||||||
|
// mark the key as used if we're tracking metadata.
|
||||||
|
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 {
|
||||||
|
dataVal := reflect.ValueOf(data)
|
||||||
|
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.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:
|
||||||
|
dataType := dataVal.Type()
|
||||||
|
elemKind := dataType.Elem().Kind()
|
||||||
|
switch {
|
||||||
|
case elemKind == reflect.Uint8:
|
||||||
|
val.SetString(string(dataVal.Interface().([]uint8)))
|
||||||
|
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.ValueOf(data)
|
||||||
|
dataKind := getKind(dataVal)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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.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.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.ValueOf(data)
|
||||||
|
dataKind := getKind(dataVal)
|
||||||
|
|
||||||
|
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(float64(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)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
dataVal := reflect.Indirect(reflect.ValueOf(data))
|
||||||
|
if dataVal.Kind() != reflect.Map {
|
||||||
|
// Accept empty array/slice instead of an empty map in weakly typed mode
|
||||||
|
if d.config.WeaklyTypedInput &&
|
||||||
|
(dataVal.Kind() == reflect.Slice || dataVal.Kind() == reflect.Array) &&
|
||||||
|
dataVal.Len() == 0 {
|
||||||
|
val.Set(valMap)
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulate errors
|
||||||
|
errors := make([]string, 0)
|
||||||
|
|
||||||
|
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) decodePtr(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.
|
||||||
|
valType := val.Type()
|
||||||
|
valElemType := valType.Elem()
|
||||||
|
realVal := reflect.New(valElemType)
|
||||||
|
if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val.Set(realVal)
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Check input type
|
||||||
|
if dataValKind != reflect.Array && dataValKind != reflect.Slice {
|
||||||
|
// Accept empty map instead of array/slice in weakly typed mode
|
||||||
|
if d.config.WeaklyTypedInput && dataVal.Kind() == reflect.Map && dataVal.Len() == 0 {
|
||||||
|
val.Set(reflect.MakeSlice(sliceType, 0, 0))
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"'%s': source data must be an array or slice, got %s", name, dataValKind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
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) 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()
|
||||||
|
if dataValKind != reflect.Map {
|
||||||
|
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataValKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
fields := make(map[*reflect.StructField]reflect.Value)
|
||||||
|
for len(structs) > 0 {
|
||||||
|
structVal := structs[0]
|
||||||
|
structs = structs[1:]
|
||||||
|
|
||||||
|
structType := structVal.Type()
|
||||||
|
for i := 0; i < structType.NumField(); i++ {
|
||||||
|
fieldType := structType.Field(i)
|
||||||
|
|
||||||
|
if fieldType.Anonymous {
|
||||||
|
fieldKind := fieldType.Type.Kind()
|
||||||
|
if fieldKind != reflect.Struct {
|
||||||
|
errors = appendErrors(errors,
|
||||||
|
fmt.Errorf("%s: unsupported type: %s", fieldType.Name, fieldKind))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have an embedded field. We "squash" the fields down
|
||||||
|
// if specified in the tag.
|
||||||
|
squash := false
|
||||||
|
tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",")
|
||||||
|
for _, tag := range tagParts[1:] {
|
||||||
|
if tag == "squash" {
|
||||||
|
squash = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if squash {
|
||||||
|
structs = append(structs, val.FieldByName(fieldType.Name))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal struct field, store it away
|
||||||
|
fields[&fieldType] = structVal.Field(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for fieldType, field := range fields {
|
||||||
|
fieldName := fieldType.Name
|
||||||
|
|
||||||
|
tagValue := fieldType.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 !field.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 !field.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(), field); 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
|
||||||
|
}
|
||||||
|
}
|
243
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_benchmark_test.go
generated
vendored
Normal file
243
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_benchmark_test.go
generated
vendored
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
package mapstructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
47
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_bugs_test.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_bugs_test.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package mapstructure
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// GH-1
|
||||||
|
func TestDecode_NilValue(t *testing.T) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"vfoo": nil,
|
||||||
|
"vother": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Map
|
||||||
|
err := Decode(input, &result)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vfoo != "" {
|
||||||
|
t.Fatalf("value should be default: %s", result.Vfoo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vother != nil {
|
||||||
|
t.Fatalf("Vother should be nil: %s", result.Vother)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GH-10
|
||||||
|
func TestDecode_mapInterfaceInterface(t *testing.T) {
|
||||||
|
input := map[interface{}]interface{}{
|
||||||
|
"vfoo": nil,
|
||||||
|
"vother": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Map
|
||||||
|
err := Decode(input, &result)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vfoo != "" {
|
||||||
|
t.Fatalf("value should be default: %s", result.Vfoo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vother != nil {
|
||||||
|
t.Fatalf("Vother should be nil: %s", result.Vother)
|
||||||
|
}
|
||||||
|
}
|
203
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_examples_test.go
generated
vendored
Normal file
203
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_examples_test.go
generated
vendored
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
package 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
|
||||||
|
}
|
954
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_test.go
generated
vendored
Normal file
954
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_test.go
generated
vendored
Normal file
|
@ -0,0 +1,954 @@
|
||||||
|
package mapstructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Basic struct {
|
||||||
|
Vstring string
|
||||||
|
Vint int
|
||||||
|
Vuint uint
|
||||||
|
Vbool bool
|
||||||
|
Vfloat float64
|
||||||
|
Vextra string
|
||||||
|
vsilent bool
|
||||||
|
Vdata interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Embedded struct {
|
||||||
|
Basic
|
||||||
|
Vunique string
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmbeddedPointer struct {
|
||||||
|
*Basic
|
||||||
|
Vunique string
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmbeddedSquash struct {
|
||||||
|
Basic `mapstructure:",squash"`
|
||||||
|
Vunique string
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Slice struct {
|
||||||
|
Vfoo string
|
||||||
|
Vbar []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SliceOfStruct struct {
|
||||||
|
Value []Basic
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
StringToInt int
|
||||||
|
StringToUint uint
|
||||||
|
StringToBool bool
|
||||||
|
StringToFloat float32
|
||||||
|
SliceToMap map[string]interface{}
|
||||||
|
MapToSlice []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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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.Fatal("should get error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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{} = nil
|
||||||
|
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_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"),
|
||||||
|
"StringToInt": "42",
|
||||||
|
"StringToUint": "42",
|
||||||
|
"StringToBool": "1",
|
||||||
|
"StringToFloat": "42.42",
|
||||||
|
"SliceToMap": []interface{}{},
|
||||||
|
"MapToSlice": 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",
|
||||||
|
StringToInt: 42,
|
||||||
|
StringToUint: 42,
|
||||||
|
StringToBool: true,
|
||||||
|
StringToFloat: 42.42,
|
||||||
|
SliceToMap: map[string]interface{}{},
|
||||||
|
MapToSlice: []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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 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 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue