pax_global_header00006660000000000000000000000064146741165040014522gustar00rootroot0000000000000052 comment=c29fc28e7927f11614d6f7e0fbf108a5c75750bb mapstructure-2.2.1/000077500000000000000000000000001467411650400142625ustar00rootroot00000000000000mapstructure-2.2.1/.editorconfig000066400000000000000000000003551467411650400167420ustar00rootroot00000000000000root = true [*] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = true trim_trailing_whitespace = true [*.go] indent_style = tab [{Makefile,*.mk}] indent_style = tab [*.nix] indent_size = 2 mapstructure-2.2.1/.envrc000066400000000000000000000003471467411650400154040ustar00rootroot00000000000000if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4=" fi use flake . --impure mapstructure-2.2.1/.github/000077500000000000000000000000001467411650400156225ustar00rootroot00000000000000mapstructure-2.2.1/.github/.editorconfig000066400000000000000000000000411467411650400202720ustar00rootroot00000000000000[{*.yml,*.yaml}] indent_size = 2 mapstructure-2.2.1/.github/dependabot.yaml000066400000000000000000000003031467411650400206070ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: gomod directory: / schedule: interval: daily - package-ecosystem: github-actions directory: / schedule: interval: daily mapstructure-2.2.1/.github/workflows/000077500000000000000000000000001467411650400176575ustar00rootroot00000000000000mapstructure-2.2.1/.github/workflows/ci.yaml000066400000000000000000000020411467411650400211330ustar00rootroot00000000000000name: CI on: push: branches: [main] pull_request: jobs: test: name: Test runs-on: ubuntu-latest strategy: matrix: go: ["1.18", "1.19", "1.20", "1.21", "1.22", "stable", "oldstable"] steps: - name: Checkout repository uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Go uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: go-version: ${{ matrix.go }} - name: Test run: go test -race -v -shuffle=on ./... lint: name: Lint runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Go uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: go-version: "1.22" - name: Lint uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0 with: version: v1.59.0 mapstructure-2.2.1/.gitignore000066400000000000000000000001011467411650400162420ustar00rootroot00000000000000/.devenv/ /.direnv/ /.pre-commit-config.yaml /bin/ /build/ /var/ mapstructure-2.2.1/.golangci.yaml000066400000000000000000000005611467411650400170110ustar00rootroot00000000000000run: timeout: 5m linters-settings: gci: sections: - standard - default - prefix(github.com/go-viper/mapstructure) golint: min-confidence: 0 goimports: local-prefixes: github.com/go-viper/maptstructure linters: disable-all: true enable: - gci - gofmt - gofumpt - goimports - staticcheck # - stylecheck mapstructure-2.2.1/CHANGELOG.md000066400000000000000000000062251467411650400161000ustar00rootroot00000000000000> [!WARNING] > As of v2 of this library, change log can be found in GitHub releases. ## 1.5.1 * Wrap errors so they're compatible with `errors.Is` and `errors.As` [GH-282] * Fix map of slices not decoding properly in certain cases. [GH-266] ## 1.5.0 * New option `IgnoreUntaggedFields` to ignore decoding to any fields without `mapstructure` (or the configured tag name) set [GH-277] * New option `ErrorUnset` which makes it an error if any fields in a target struct are not set by the decoding process. [GH-225] * New function `OrComposeDecodeHookFunc` to help compose decode hooks. [GH-240] * Decoding to slice from array no longer crashes [GH-265] * Decode nested struct pointers to map [GH-271] * Fix issue where `,squash` was ignored if `Squash` option was set. [GH-280] * Fix issue where fields with `,omitempty` would sometimes decode into a map with an empty string key [GH-281] ## 1.4.3 * Fix cases where `json.Number` didn't decode properly [GH-261] ## 1.4.2 * Custom name matchers to support any sort of casing, formatting, etc. for field names. [GH-250] * Fix possible panic in ComposeDecodeHookFunc [GH-251] ## 1.4.1 * Fix regression where `*time.Time` value would be set to empty and not be sent to decode hooks properly [GH-232] ## 1.4.0 * A new decode hook type `DecodeHookFuncValue` has been added that has access to the full values. [GH-183] * Squash is now supported with embedded fields that are struct pointers [GH-205] * Empty strings will convert to 0 for all numeric types when weakly decoding [GH-206] ## 1.3.3 * Decoding maps from maps creates a settable value for decode hooks [GH-203] ## 1.3.2 * Decode into interface type with a struct value is supported [GH-187] ## 1.3.1 * Squash should only squash embedded structs. [GH-194] ## 1.3.0 * Added `",omitempty"` support. This will ignore zero values in the source structure when encoding. [GH-145] ## 1.2.3 * Fix duplicate entries in Keys list with pointer values. [GH-185] ## 1.2.2 * Do not add unsettable (unexported) values to the unused metadata key or "remain" value. [GH-150] ## 1.2.1 * Go modules checksum mismatch fix ## 1.2.0 * Added support to capture unused values in a field using the `",remain"` value in the mapstructure tag. There is an example to showcase usage. * Added `DecoderConfig` option to always squash embedded structs * `json.Number` can decode into `uint` types * Empty slices are preserved and not replaced with nil slices * Fix panic that can occur in when decoding a map into a nil slice of structs * Improved package documentation for godoc ## 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-2.2.1/LICENSE000066400000000000000000000020751467411650400152730ustar00rootroot00000000000000The 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-2.2.1/README.md000066400000000000000000000065551467411650400155540ustar00rootroot00000000000000# mapstructure [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/go-viper/mapstructure/ci.yaml?branch=main&style=flat-square)](https://github.com/go-viper/mapstructure/actions?query=workflow%3ACI) [![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/mod/github.com/go-viper/mapstructure/v2) ![Go Version](https://img.shields.io/badge/go%20version-%3E=1.18-61CFDD.svg?style=flat-square) 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 ```shell go get github.com/go-viper/mapstructure/v2 ``` ## Migrating from `github.com/mitchellh/mapstructure` [@mitchehllh](https://github.com/mitchellh) announced his intent to archive some of his unmaintained projects (see [here](https://gist.github.com/mitchellh/90029601268e59a29e64e55bab1c5bdc) and [here](https://github.com/mitchellh/mapstructure/issues/349)). This is a repository achieved the "blessed fork" status. You can migrate to this package by changing your import paths in your Go files to `github.com/go-viper/mapstructure/v2`. The API is the same, so you don't need to change anything else. Here is a script that can help you with the migration: ```shell sed -i 's/github.com\/mitchellh\/mapstructure/github.com\/go-viper\/mapstructure\/v2/g' $(find . -type f -name '*.go') ``` If you need more time to migrate your code, that is absolutely fine. Some of the latest fixes are backported to the v1 release branch of this package, so you can use the Go modules `replace` feature until you are ready to migrate: ```shell replace github.com/mitchellh/mapstructure => github.com/go-viper/mapstructure v1.6.0 ``` ## Usage & Example For usage and examples see the [documentation](https://pkg.go.dev/mod/github.com/go-viper/mapstructure/v2). 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. ## Credits Mapstructure was originally created by [@mitchellh](https://github.com/mitchellh). This is a maintained fork of the original library. Read more about the reasons for the fork [here](https://github.com/mitchellh/mapstructure/issues/349). ## License The project is licensed under the [MIT License](LICENSE). mapstructure-2.2.1/decode_hooks.go000066400000000000000000000411351467411650400172430ustar00rootroot00000000000000package mapstructure import ( "encoding" "errors" "fmt" "net" "net/netip" "net/url" "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 var f3 DecodeHookFuncValue // Fill in the variables into this interface and the rest is done // automatically using the reflect package. potential := []interface{}{f1, f2, f3} 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 } // cachedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns // it into a closure to be used directly // if the type fails to convert we return a closure always erroring to keep the previous behaviour func cachedDecodeHook(raw DecodeHookFunc) func(from reflect.Value, to reflect.Value) (interface{}, error) { switch f := typedDecodeHook(raw).(type) { case DecodeHookFuncType: return func(from reflect.Value, to reflect.Value) (interface{}, error) { return f(from.Type(), to.Type(), from.Interface()) } case DecodeHookFuncKind: return func(from reflect.Value, to reflect.Value) (interface{}, error) { return f(from.Kind(), to.Kind(), from.Interface()) } case DecodeHookFuncValue: return func(from reflect.Value, to reflect.Value) (interface{}, error) { return f(from, to) } default: return func(from reflect.Value, to reflect.Value) (interface{}, error) { return nil, errors.New("invalid decode hook signature") } } } // 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.Value, to reflect.Value, ) (interface{}, error) { switch f := typedDecodeHook(raw).(type) { case DecodeHookFuncType: return f(from.Type(), to.Type(), from.Interface()) case DecodeHookFuncKind: return f(from.Kind(), to.Kind(), from.Interface()) case DecodeHookFuncValue: return f(from, to) 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 { cached := make([]func(from reflect.Value, to reflect.Value) (interface{}, error), 0, len(fs)) for _, f := range fs { cached = append(cached, cachedDecodeHook(f)) } return func(f reflect.Value, t reflect.Value) (interface{}, error) { var err error data := f.Interface() newFrom := f for _, c := range cached { data, err = c(newFrom, t) if err != nil { return nil, err } newFrom = reflect.ValueOf(data) } return data, nil } } // OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned. // If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages. func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc { cached := make([]func(from reflect.Value, to reflect.Value) (interface{}, error), 0, len(ff)) for _, f := range ff { cached = append(cached, cachedDecodeHook(f)) } return func(a, b reflect.Value) (interface{}, error) { var allErrs string var out interface{} var err error for _, c := range cached { out, err = c(a, b) if err != nil { allErrs += err.Error() + "\n" continue } return out, nil } return nil, errors.New(allErrs) } } // StringToSliceHookFunc returns a DecodeHookFunc that converts // string to []string by splitting on the given sep. func StringToSliceHookFunc(sep 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.SliceOf(f) { 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)) } } // StringToURLHookFunc returns a DecodeHookFunc that converts // strings to *url.URL. func StringToURLHookFunc() 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(&url.URL{}) { return data, nil } // Convert it by parsing return url.Parse(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 } func RecursiveStructToMapHookFunc() DecodeHookFunc { return func(f reflect.Value, t reflect.Value) (interface{}, error) { if f.Kind() != reflect.Struct { return f.Interface(), nil } var i interface{} = struct{}{} if t.Type() != reflect.TypeOf(&i).Elem() { return f.Interface(), nil } m := make(map[string]interface{}) t.Set(reflect.ValueOf(m)) return f.Interface(), nil } } // TextUnmarshallerHookFunc returns a DecodeHookFunc that applies // strings to the UnmarshalText function, when the target type // implements the encoding.TextUnmarshaler interface func TextUnmarshallerHookFunc() DecodeHookFuncType { return func( f reflect.Type, t reflect.Type, data interface{}, ) (interface{}, error) { if f.Kind() != reflect.String { return data, nil } result := reflect.New(t).Interface() unmarshaller, ok := result.(encoding.TextUnmarshaler) if !ok { return data, nil } str, ok := data.(string) if !ok { str = reflect.Indirect(reflect.ValueOf(&data)).Elem().String() } if err := unmarshaller.UnmarshalText([]byte(str)); err != nil { return nil, err } return result, nil } } // StringToNetIPAddrHookFunc returns a DecodeHookFunc that converts // strings to netip.Addr. func StringToNetIPAddrHookFunc() 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(netip.Addr{}) { return data, nil } // Convert it by parsing return netip.ParseAddr(data.(string)) } } // StringToNetIPAddrPortHookFunc returns a DecodeHookFunc that converts // strings to netip.AddrPort. func StringToNetIPAddrPortHookFunc() 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(netip.AddrPort{}) { return data, nil } // Convert it by parsing return netip.ParseAddrPort(data.(string)) } } // StringToBasicTypeHookFunc returns a DecodeHookFunc that converts // strings to basic types. // int8, uint8, int16, uint16, int32, uint32, int64, uint64, int, uint, float32, float64, bool, byte, rune, complex64, complex128 func StringToBasicTypeHookFunc() DecodeHookFunc { return ComposeDecodeHookFunc( StringToInt8HookFunc(), StringToUint8HookFunc(), StringToInt16HookFunc(), StringToUint16HookFunc(), StringToInt32HookFunc(), StringToUint32HookFunc(), StringToInt64HookFunc(), StringToUint64HookFunc(), StringToIntHookFunc(), StringToUintHookFunc(), StringToFloat32HookFunc(), StringToFloat64HookFunc(), StringToBoolHookFunc(), // byte and rune are aliases for uint8 and int32 respectively // StringToByteHookFunc(), // StringToRuneHookFunc(), StringToComplex64HookFunc(), StringToComplex128HookFunc(), ) } // StringToInt8HookFunc returns a DecodeHookFunc that converts // strings to int8. func StringToInt8HookFunc() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String || t.Kind() != reflect.Int8 { return data, nil } // Convert it by parsing i64, err := strconv.ParseInt(data.(string), 0, 8) return int8(i64), err } } // StringToUint8HookFunc returns a DecodeHookFunc that converts // strings to uint8. func StringToUint8HookFunc() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String || t.Kind() != reflect.Uint8 { return data, nil } // Convert it by parsing u64, err := strconv.ParseUint(data.(string), 0, 8) return uint8(u64), err } } // StringToInt16HookFunc returns a DecodeHookFunc that converts // strings to int16. func StringToInt16HookFunc() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String || t.Kind() != reflect.Int16 { return data, nil } // Convert it by parsing i64, err := strconv.ParseInt(data.(string), 0, 16) return int16(i64), err } } // StringToUint16HookFunc returns a DecodeHookFunc that converts // strings to uint16. func StringToUint16HookFunc() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String || t.Kind() != reflect.Uint16 { return data, nil } // Convert it by parsing u64, err := strconv.ParseUint(data.(string), 0, 16) return uint16(u64), err } } // StringToInt32HookFunc returns a DecodeHookFunc that converts // strings to int32. func StringToInt32HookFunc() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String || t.Kind() != reflect.Int32 { return data, nil } // Convert it by parsing i64, err := strconv.ParseInt(data.(string), 0, 32) return int32(i64), err } } // StringToUint32HookFunc returns a DecodeHookFunc that converts // strings to uint32. func StringToUint32HookFunc() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String || t.Kind() != reflect.Uint32 { return data, nil } // Convert it by parsing u64, err := strconv.ParseUint(data.(string), 0, 32) return uint32(u64), err } } // StringToInt64HookFunc returns a DecodeHookFunc that converts // strings to int64. func StringToInt64HookFunc() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String || t.Kind() != reflect.Int64 { return data, nil } // Convert it by parsing return strconv.ParseInt(data.(string), 0, 64) } } // StringToUint64HookFunc returns a DecodeHookFunc that converts // strings to uint64. func StringToUint64HookFunc() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String || t.Kind() != reflect.Uint64 { return data, nil } // Convert it by parsing return strconv.ParseUint(data.(string), 0, 64) } } // StringToIntHookFunc returns a DecodeHookFunc that converts // strings to int. func StringToIntHookFunc() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String || t.Kind() != reflect.Int { return data, nil } // Convert it by parsing i64, err := strconv.ParseInt(data.(string), 0, 0) return int(i64), err } } // StringToUintHookFunc returns a DecodeHookFunc that converts // strings to uint. func StringToUintHookFunc() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String || t.Kind() != reflect.Uint { return data, nil } // Convert it by parsing u64, err := strconv.ParseUint(data.(string), 0, 0) return uint(u64), err } } // StringToFloat32HookFunc returns a DecodeHookFunc that converts // strings to float32. func StringToFloat32HookFunc() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String || t.Kind() != reflect.Float32 { return data, nil } // Convert it by parsing f64, err := strconv.ParseFloat(data.(string), 32) return float32(f64), err } } // StringToFloat64HookFunc returns a DecodeHookFunc that converts // strings to float64. func StringToFloat64HookFunc() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String || t.Kind() != reflect.Float64 { return data, nil } // Convert it by parsing return strconv.ParseFloat(data.(string), 64) } } // StringToBoolHookFunc returns a DecodeHookFunc that converts // strings to bool. func StringToBoolHookFunc() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String || t.Kind() != reflect.Bool { return data, nil } // Convert it by parsing return strconv.ParseBool(data.(string)) } } // StringToByteHookFunc returns a DecodeHookFunc that converts // strings to byte. func StringToByteHookFunc() DecodeHookFunc { return StringToUint8HookFunc() } // StringToRuneHookFunc returns a DecodeHookFunc that converts // strings to rune. func StringToRuneHookFunc() DecodeHookFunc { return StringToInt32HookFunc() } // StringToComplex64HookFunc returns a DecodeHookFunc that converts // strings to complex64. func StringToComplex64HookFunc() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String || t.Kind() != reflect.Complex64 { return data, nil } // Convert it by parsing c128, err := strconv.ParseComplex(data.(string), 64) return complex64(c128), err } } // StringToComplex128HookFunc returns a DecodeHookFunc that converts // strings to complex128. func StringToComplex128HookFunc() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String || t.Kind() != reflect.Complex128 { return data, nil } // Convert it by parsing return strconv.ParseComplex(data.(string), 128) } } mapstructure-2.2.1/decode_hooks_test.go000066400000000000000000000776761467411650400203250ustar00rootroot00000000000000package mapstructure import ( "encoding/json" "errors" "math/big" "net" "net/netip" "net/url" "reflect" "strings" "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.ValueOf(""), reflect.ValueOf([]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.ValueOf(""), reflect.ValueOf([]byte(""))) 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.ValueOf(""), reflect.ValueOf([]byte(""))) if err != nil { t.Fatalf("bad: %s", err) } if f2From != reflect.Int { t.Fatalf("bad: %#v", f2From) } } func TestOrComposeDecodeHookFunc(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 := OrComposeDecodeHookFunc(f1, f2) result, err := DecodeHookExec( f, reflect.ValueOf(""), reflect.ValueOf([]byte(""))) if err != nil { t.Fatalf("bad: %s", err) } if result.(string) != "foo" { t.Fatalf("bad: %#v", result) } } func TestOrComposeDecodeHookFunc_correctValueIsLast(t *testing.T) { f1 := func( f reflect.Kind, t reflect.Kind, data interface{}, ) (interface{}, error) { return nil, errors.New("f1 error") } f2 := func( f reflect.Kind, t reflect.Kind, data interface{}, ) (interface{}, error) { return nil, errors.New("f2 error") } f3 := func( f reflect.Kind, t reflect.Kind, data interface{}, ) (interface{}, error) { return data.(string) + "bar", nil } f := OrComposeDecodeHookFunc(f1, f2, f3) result, err := DecodeHookExec( f, reflect.ValueOf(""), reflect.ValueOf([]byte(""))) if err != nil { t.Fatalf("bad: %s", err) } if result.(string) != "bar" { t.Fatalf("bad: %#v", result) } } func TestOrComposeDecodeHookFunc_err(t *testing.T) { f1 := func( f reflect.Kind, t reflect.Kind, data interface{}, ) (interface{}, error) { return nil, errors.New("f1 error") } f2 := func( f reflect.Kind, t reflect.Kind, data interface{}, ) (interface{}, error) { return nil, errors.New("f2 error") } f := OrComposeDecodeHookFunc(f1, f2) _, err := DecodeHookExec( f, reflect.ValueOf(""), reflect.ValueOf([]byte(""))) if err == nil { t.Fatalf("bad: should return an error") } if err.Error() != "f1 error\nf2 error\n" { t.Fatalf("bad: %s", err) } } func TestComposeDecodeHookFunc_safe_nofuncs(t *testing.T) { f := ComposeDecodeHookFunc() type myStruct2 struct { MyInt int } type myStruct1 struct { Blah map[string]myStruct2 } src := &myStruct1{Blah: map[string]myStruct2{ "test": { MyInt: 1, }, }} dst := &myStruct1{} dConf := &DecoderConfig{ Result: dst, ErrorUnused: true, DecodeHook: f, } d, err := NewDecoder(dConf) if err != nil { t.Fatal(err) } err = d.Decode(src) if err != nil { t.Fatal(err) } } func TestStringToSliceHookFunc(t *testing.T) { f := StringToSliceHookFunc(",") strValue := reflect.ValueOf("42") sliceValue := reflect.ValueOf([]string{"42"}) cases := []struct { f, t reflect.Value result interface{} err bool }{ {sliceValue, sliceValue, []string{"42"}, false}, {reflect.ValueOf([]byte("42")), reflect.ValueOf([]byte{}), []byte("42"), false}, {strValue, strValue, "42", false}, { reflect.ValueOf("foo,bar,baz"), sliceValue, []string{"foo", "bar", "baz"}, false, }, { reflect.ValueOf(""), sliceValue, []string{}, false, }, } for i, tc := range cases { actual, err := DecodeHookExec(f, tc.f, tc.t) 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() timeValue := reflect.ValueOf(time.Duration(5)) strValue := reflect.ValueOf("") cases := []struct { f, t reflect.Value result interface{} err bool }{ {reflect.ValueOf("5s"), timeValue, 5 * time.Second, false}, {reflect.ValueOf("5"), timeValue, time.Duration(0), true}, {reflect.ValueOf("5"), strValue, "5", false}, } for i, tc := range cases { actual, err := DecodeHookExec(f, tc.f, tc.t) 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 TestStringToURLHookFunc(t *testing.T) { f := StringToURLHookFunc() urlSample, _ := url.Parse("http://example.com") urlValue := reflect.ValueOf(urlSample) strValue := reflect.ValueOf("http://example.com") cases := []struct { f, t reflect.Value result interface{} err bool }{ {reflect.ValueOf("http://example.com"), urlValue, urlSample, false}, {reflect.ValueOf("http ://example.com"), urlValue, (*url.URL)(nil), true}, {reflect.ValueOf("http://example.com"), strValue, "http://example.com", false}, } for i, tc := range cases { actual, err := DecodeHookExec(f, tc.f, tc.t) 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) { strValue := reflect.ValueOf("5") timeValue := reflect.ValueOf(time.Time{}) cases := []struct { f, t reflect.Value layout string result interface{} err bool }{ { reflect.ValueOf("2006-01-02T15:04:05Z"), timeValue, time.RFC3339, time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC), false, }, {strValue, timeValue, time.RFC3339, time.Time{}, true}, {strValue, strValue, time.RFC3339, "5", false}, } for i, tc := range cases { f := StringToTimeHookFunc(tc.layout) actual, err := DecodeHookExec(f, tc.f, tc.t) 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) { strValue := reflect.ValueOf("5") ipValue := reflect.ValueOf(net.IP{}) cases := []struct { f, t reflect.Value result interface{} err bool }{ { reflect.ValueOf("1.2.3.4"), ipValue, net.IPv4(0x01, 0x02, 0x03, 0x04), false, }, {strValue, ipValue, net.IP{}, true}, {strValue, strValue, "5", false}, } for i, tc := range cases { f := StringToIPHookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) 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) { strValue := reflect.ValueOf("5") ipNetValue := reflect.ValueOf(net.IPNet{}) var nilNet *net.IPNet = nil cases := []struct { f, t reflect.Value result interface{} err bool }{ { reflect.ValueOf("1.2.3.4/24"), ipNetValue, &net.IPNet{ IP: net.IP{0x01, 0x02, 0x03, 0x00}, Mask: net.IPv4Mask(0xff, 0xff, 0xff, 0x00), }, false, }, {strValue, ipNetValue, nilNet, true}, {strValue, strValue, "5", false}, } for i, tc := range cases { f := StringToIPNetHookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) 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 strValue := reflect.ValueOf("") cases := []struct { f, t reflect.Value result interface{} err bool }{ // TO STRING { reflect.ValueOf(false), strValue, "0", false, }, { reflect.ValueOf(true), strValue, "1", false, }, { reflect.ValueOf(float32(7)), strValue, "7", false, }, { reflect.ValueOf(int(7)), strValue, "7", false, }, { reflect.ValueOf([]uint8("foo")), strValue, "foo", false, }, { reflect.ValueOf(uint(7)), strValue, "7", false, }, } for i, tc := range cases { actual, err := DecodeHookExec(f, tc.f, tc.t) 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 TestStructToMapHookFuncTabled(t *testing.T) { var f DecodeHookFunc = RecursiveStructToMapHookFunc() type b struct { TestKey string } type a struct { Sub b } testStruct := a{ Sub: b{ TestKey: "testval", }, } testMap := map[string]interface{}{ "Sub": map[string]interface{}{ "TestKey": "testval", }, } cases := []struct { name string receiver interface{} input interface{} expected interface{} err bool }{ { "map receiver", func() interface{} { var res map[string]interface{} return &res }(), testStruct, &testMap, false, }, { "interface receiver", func() interface{} { var res interface{} return &res }(), testStruct, func() interface{} { var exp interface{} = testMap return &exp }(), false, }, { "slice receiver errors", func() interface{} { var res []string return &res }(), testStruct, new([]string), true, }, { "slice to slice - no change", func() interface{} { var res []string return &res }(), []string{"a", "b"}, &[]string{"a", "b"}, false, }, { "string to string - no change", func() interface{} { var res string return &res }(), "test", func() *string { s := "test" return &s }(), false, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { cfg := &DecoderConfig{ DecodeHook: f, Result: tc.receiver, } d, err := NewDecoder(cfg) if err != nil { t.Fatalf("unexpected err %#v", err) } err = d.Decode(tc.input) if tc.err != (err != nil) { t.Fatalf("expected err %#v", err) } if !reflect.DeepEqual(tc.expected, tc.receiver) { t.Fatalf("expected %#v, got %#v", tc.expected, tc.receiver) } }) } } func TestTextUnmarshallerHookFunc(t *testing.T) { type MyString string cases := []struct { f, t reflect.Value result interface{} err bool }{ {reflect.ValueOf("42"), reflect.ValueOf(big.Int{}), big.NewInt(42), false}, {reflect.ValueOf("invalid"), reflect.ValueOf(big.Int{}), nil, true}, {reflect.ValueOf("5"), reflect.ValueOf("5"), "5", false}, {reflect.ValueOf(json.Number("42")), reflect.ValueOf(big.Int{}), big.NewInt(42), false}, {reflect.ValueOf(MyString("42")), reflect.ValueOf(big.Int{}), big.NewInt(42), false}, } for i, tc := range cases { f := TextUnmarshallerHookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) 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 TestStringToNetIPAddrHookFunc(t *testing.T) { strValue := reflect.ValueOf("5") addrValue := reflect.ValueOf(netip.Addr{}) cases := []struct { f, t reflect.Value result interface{} err bool }{ { reflect.ValueOf("192.0.2.1"), addrValue, netip.AddrFrom4([4]byte{0xc0, 0x00, 0x02, 0x01}), false, }, {strValue, addrValue, netip.Addr{}, true}, {strValue, strValue, "5", false}, } for i, tc := range cases { f := StringToNetIPAddrHookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) 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 TestStringToNetIPAddrPortHookFunc(t *testing.T) { strValue := reflect.ValueOf("5") addrPortValue := reflect.ValueOf(netip.AddrPort{}) cases := []struct { f, t reflect.Value result interface{} err bool }{ { reflect.ValueOf("192.0.2.1:80"), addrPortValue, netip.AddrPortFrom(netip.AddrFrom4([4]byte{0xc0, 0x00, 0x02, 0x01}), 80), false, }, {strValue, addrPortValue, netip.AddrPort{}, true}, {strValue, strValue, "5", false}, } for i, tc := range cases { f := StringToNetIPAddrPortHookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) 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 TestStringToBasicTypeHookFunc(t *testing.T) { strValue := reflect.ValueOf("42") cases := []struct { f, t reflect.Value result interface{} err bool }{ {strValue, strValue, "42", false}, {strValue, reflect.ValueOf(int8(0)), int8(42), false}, {strValue, reflect.ValueOf(uint8(0)), uint8(42), false}, {strValue, reflect.ValueOf(int16(0)), int16(42), false}, {strValue, reflect.ValueOf(uint16(0)), uint16(42), false}, {strValue, reflect.ValueOf(int32(0)), int32(42), false}, {strValue, reflect.ValueOf(uint32(0)), uint32(42), false}, {strValue, reflect.ValueOf(int64(0)), int64(42), false}, {strValue, reflect.ValueOf(uint64(0)), uint64(42), false}, {strValue, reflect.ValueOf(int(0)), int(42), false}, {strValue, reflect.ValueOf(uint(0)), uint(42), false}, {strValue, reflect.ValueOf(float32(0)), float32(42), false}, {strValue, reflect.ValueOf(float64(0)), float64(42), false}, {reflect.ValueOf("true"), reflect.ValueOf(bool(false)), true, false}, {strValue, reflect.ValueOf(byte(0)), byte(42), false}, {strValue, reflect.ValueOf(rune(0)), rune(42), false}, {strValue, reflect.ValueOf(complex64(0)), complex64(42), false}, {strValue, reflect.ValueOf(complex128(0)), complex128(42), false}, } for i, tc := range cases { f := StringToBasicTypeHookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !tc.err && !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToInt8HookFunc(t *testing.T) { strValue := reflect.ValueOf("42") int8Value := reflect.ValueOf(int8(0)) cases := []struct { f, t reflect.Value result interface{} err bool }{ {strValue, int8Value, int8(42), false}, {strValue, strValue, "42", false}, {reflect.ValueOf(strings.Repeat("42", 42)), int8Value, int8(0), true}, {reflect.ValueOf("42.42"), int8Value, int8(0), true}, {reflect.ValueOf("-42"), int8Value, int8(-42), false}, {reflect.ValueOf("0b101010"), int8Value, int8(42), false}, {reflect.ValueOf("052"), int8Value, int8(42), false}, {reflect.ValueOf("0o52"), int8Value, int8(42), false}, {reflect.ValueOf("0x2a"), int8Value, int8(42), false}, {reflect.ValueOf("0X2A"), int8Value, int8(42), false}, {reflect.ValueOf("0"), int8Value, int8(0), false}, {reflect.ValueOf("0.0"), int8Value, int8(0), true}, } for i, tc := range cases { f := StringToInt8HookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !tc.err && !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToUint8HookFunc(t *testing.T) { strValue := reflect.ValueOf("42") uint8Value := reflect.ValueOf(uint8(0)) cases := []struct { f, t reflect.Value result interface{} err bool }{ {strValue, uint8Value, uint8(42), false}, {strValue, strValue, "42", false}, {reflect.ValueOf(strings.Repeat("42", 42)), uint8Value, uint8(0), true}, {reflect.ValueOf("42.42"), uint8Value, uint8(0), true}, {reflect.ValueOf("-42"), uint8Value, uint8(0), true}, {reflect.ValueOf("0b101010"), uint8Value, uint8(42), false}, {reflect.ValueOf("052"), uint8Value, uint8(42), false}, {reflect.ValueOf("0o52"), uint8Value, uint8(42), false}, {reflect.ValueOf("0x2a"), uint8Value, uint8(42), false}, {reflect.ValueOf("0X2A"), uint8Value, uint8(42), false}, {reflect.ValueOf("0"), uint8Value, uint8(0), false}, {reflect.ValueOf("0.0"), uint8Value, uint8(0), true}, } for i, tc := range cases { f := StringToUint8HookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !tc.err && !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToInt16HookFunc(t *testing.T) { strValue := reflect.ValueOf("42") int16Value := reflect.ValueOf(int16(0)) cases := []struct { f, t reflect.Value result interface{} err bool }{ {strValue, int16Value, int16(42), false}, {strValue, strValue, "42", false}, {reflect.ValueOf(strings.Repeat("42", 42)), int16Value, int16(0), true}, {reflect.ValueOf("42.42"), int16Value, int16(0), true}, {reflect.ValueOf("-42"), int16Value, int16(-42), false}, {reflect.ValueOf("0b101010"), int16Value, int16(42), false}, {reflect.ValueOf("052"), int16Value, int16(42), false}, {reflect.ValueOf("0o52"), int16Value, int16(42), false}, {reflect.ValueOf("0x2a"), int16Value, int16(42), false}, {reflect.ValueOf("0X2A"), int16Value, int16(42), false}, {reflect.ValueOf("0"), int16Value, int16(0), false}, {reflect.ValueOf("0.0"), int16Value, int16(0), true}, } for i, tc := range cases { f := StringToInt16HookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !tc.err && !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToUint16HookFunc(t *testing.T) { strValue := reflect.ValueOf("42") uint16Value := reflect.ValueOf(uint16(0)) cases := []struct { f, t reflect.Value result interface{} err bool }{ {strValue, uint16Value, uint16(42), false}, {strValue, strValue, "42", false}, {reflect.ValueOf(strings.Repeat("42", 42)), uint16Value, uint16(0), true}, {reflect.ValueOf("42.42"), uint16Value, uint16(0), true}, {reflect.ValueOf("-42"), uint16Value, uint16(0), true}, {reflect.ValueOf("0b101010"), uint16Value, uint16(42), false}, {reflect.ValueOf("052"), uint16Value, uint16(42), false}, {reflect.ValueOf("0o52"), uint16Value, uint16(42), false}, {reflect.ValueOf("0x2a"), uint16Value, uint16(42), false}, {reflect.ValueOf("0X2A"), uint16Value, uint16(42), false}, {reflect.ValueOf("0"), uint16Value, uint16(0), false}, {reflect.ValueOf("0.0"), uint16Value, uint16(0), true}, } for i, tc := range cases { f := StringToUint16HookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !tc.err && !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToInt32HookFunc(t *testing.T) { strValue := reflect.ValueOf("42") int32Value := reflect.ValueOf(int32(0)) cases := []struct { f, t reflect.Value result interface{} err bool }{ {strValue, int32Value, int32(42), false}, {strValue, strValue, "42", false}, {reflect.ValueOf(strings.Repeat("42", 42)), int32Value, int32(0), true}, {reflect.ValueOf("42.42"), int32Value, int32(0), true}, {reflect.ValueOf("-42"), int32Value, int32(-42), false}, {reflect.ValueOf("0b101010"), int32Value, int32(42), false}, {reflect.ValueOf("052"), int32Value, int32(42), false}, {reflect.ValueOf("0o52"), int32Value, int32(42), false}, {reflect.ValueOf("0x2a"), int32Value, int32(42), false}, {reflect.ValueOf("0X2A"), int32Value, int32(42), false}, {reflect.ValueOf("0"), int32Value, int32(0), false}, {reflect.ValueOf("0.0"), int32Value, int32(0), true}, } for i, tc := range cases { f := StringToInt32HookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !tc.err && !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToUint32HookFunc(t *testing.T) { strValue := reflect.ValueOf("42") uint32Value := reflect.ValueOf(uint32(0)) cases := []struct { f, t reflect.Value result interface{} err bool }{ {strValue, uint32Value, uint32(42), false}, {strValue, strValue, "42", false}, {reflect.ValueOf(strings.Repeat("42", 42)), uint32Value, uint32(0), true}, {reflect.ValueOf("42.42"), uint32Value, uint32(0), true}, {reflect.ValueOf("-42"), uint32Value, uint32(0), true}, {reflect.ValueOf("0b101010"), uint32Value, uint32(42), false}, {reflect.ValueOf("052"), uint32Value, uint32(42), false}, {reflect.ValueOf("0o52"), uint32Value, uint32(42), false}, {reflect.ValueOf("0x2a"), uint32Value, uint32(42), false}, {reflect.ValueOf("0X2A"), uint32Value, uint32(42), false}, {reflect.ValueOf("0"), uint32Value, uint32(0), false}, {reflect.ValueOf("0.0"), uint32Value, uint32(0), true}, } for i, tc := range cases { f := StringToUint32HookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !tc.err && !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToInt64HookFunc(t *testing.T) { strValue := reflect.ValueOf("42") int64Value := reflect.ValueOf(int64(0)) cases := []struct { f, t reflect.Value result interface{} err bool }{ {strValue, int64Value, int64(42), false}, {strValue, strValue, "42", false}, {reflect.ValueOf(strings.Repeat("42", 42)), int64Value, int64(0), true}, {reflect.ValueOf("42.42"), int64Value, int64(0), true}, {reflect.ValueOf("-42"), int64Value, int64(-42), false}, {reflect.ValueOf("0b101010"), int64Value, int64(42), false}, {reflect.ValueOf("052"), int64Value, int64(42), false}, {reflect.ValueOf("0o52"), int64Value, int64(42), false}, {reflect.ValueOf("0x2a"), int64Value, int64(42), false}, {reflect.ValueOf("0X2A"), int64Value, int64(42), false}, {reflect.ValueOf("0"), int64Value, int64(0), false}, {reflect.ValueOf("0.0"), int64Value, int64(0), true}, } for i, tc := range cases { f := StringToInt64HookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !tc.err && !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToUint64HookFunc(t *testing.T) { strValue := reflect.ValueOf("42") uint64Value := reflect.ValueOf(uint64(0)) cases := []struct { f, t reflect.Value result interface{} err bool }{ {strValue, uint64Value, uint64(42), false}, {strValue, strValue, "42", false}, {reflect.ValueOf(strings.Repeat("42", 42)), uint64Value, uint64(0), true}, {reflect.ValueOf("42.42"), uint64Value, uint64(0), true}, {reflect.ValueOf("-42"), uint64Value, uint64(0), true}, {reflect.ValueOf("0b101010"), uint64Value, uint64(42), false}, {reflect.ValueOf("052"), uint64Value, uint64(42), false}, {reflect.ValueOf("0o52"), uint64Value, uint64(42), false}, {reflect.ValueOf("0x2a"), uint64Value, uint64(42), false}, {reflect.ValueOf("0X2A"), uint64Value, uint64(42), false}, {reflect.ValueOf("0"), uint64Value, uint64(0), false}, {reflect.ValueOf("0.0"), uint64Value, uint64(0), true}, } for i, tc := range cases { f := StringToUint64HookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !tc.err && !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToIntHookFunc(t *testing.T) { strValue := reflect.ValueOf("42") intValue := reflect.ValueOf(int(0)) cases := []struct { f, t reflect.Value result interface{} err bool }{ {strValue, intValue, int(42), false}, {strValue, strValue, "42", false}, {reflect.ValueOf(strings.Repeat("42", 42)), intValue, int(0), true}, {reflect.ValueOf("42.42"), intValue, int(0), true}, {reflect.ValueOf("-42"), intValue, int(-42), false}, {reflect.ValueOf("0b101010"), intValue, int(42), false}, {reflect.ValueOf("052"), intValue, int(42), false}, {reflect.ValueOf("0o52"), intValue, int(42), false}, {reflect.ValueOf("0x2a"), intValue, int(42), false}, {reflect.ValueOf("0X2A"), intValue, int(42), false}, {reflect.ValueOf("0"), intValue, int(0), false}, {reflect.ValueOf("0.0"), intValue, int(0), true}, } for i, tc := range cases { f := StringToIntHookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !tc.err && !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToUintHookFunc(t *testing.T) { strValue := reflect.ValueOf("42") uintValue := reflect.ValueOf(uint(0)) cases := []struct { f, t reflect.Value result interface{} err bool }{ {strValue, uintValue, uint(42), false}, {strValue, strValue, "42", false}, {reflect.ValueOf(strings.Repeat("42", 42)), uintValue, uint(0), true}, {reflect.ValueOf("42.42"), uintValue, uint(0), true}, {reflect.ValueOf("-42"), uintValue, uint(0), true}, {reflect.ValueOf("0b101010"), uintValue, uint(42), false}, {reflect.ValueOf("052"), uintValue, uint(42), false}, {reflect.ValueOf("0o52"), uintValue, uint(42), false}, {reflect.ValueOf("0x2a"), uintValue, uint(42), false}, {reflect.ValueOf("0X2A"), uintValue, uint(42), false}, {reflect.ValueOf("0"), uintValue, uint(0), false}, {reflect.ValueOf("0.0"), uintValue, uint(0), true}, } for i, tc := range cases { f := StringToUintHookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, tc.err) } if !tc.err && !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToFloat32HookFunc(t *testing.T) { strValue := reflect.ValueOf("42.42") float32Value := reflect.ValueOf(float32(0)) cases := []struct { f, t reflect.Value result interface{} err bool }{ {strValue, float32Value, float32(42.42), false}, {strValue, strValue, "42.42", false}, {reflect.ValueOf(strings.Repeat("42", 420)), float32Value, float32(0), true}, {reflect.ValueOf("42.42.42"), float32Value, float32(0), true}, {reflect.ValueOf("-42.42"), float32Value, float32(-42.42), false}, {reflect.ValueOf("0"), float32Value, float32(0), false}, {reflect.ValueOf("1e3"), float32Value, float32(1000), false}, {reflect.ValueOf("1e-3"), float32Value, float32(0.001), false}, } for i, tc := range cases { f := StringToFloat32HookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, err) } if !tc.err && !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToFloat64HookFunc(t *testing.T) { strValue := reflect.ValueOf("42.42") float64Value := reflect.ValueOf(float64(0)) cases := []struct { f, t reflect.Value result interface{} err bool }{ {strValue, float64Value, float64(42.42), false}, {strValue, strValue, "42.42", false}, {reflect.ValueOf(strings.Repeat("42", 420)), float64Value, float64(0), true}, {reflect.ValueOf("42.42.42"), float64Value, float64(0), true}, {reflect.ValueOf("-42.42"), float64Value, float64(-42.42), false}, {reflect.ValueOf("0"), float64Value, float64(0), false}, {reflect.ValueOf("0.0"), float64Value, float64(0), false}, {reflect.ValueOf("1e3"), float64Value, float64(1000), false}, {reflect.ValueOf("1e-3"), float64Value, float64(0.001), false}, } for i, tc := range cases { f := StringToFloat64HookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, err) } if !tc.err && !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToComplex64HookFunc(t *testing.T) { strValue := reflect.ValueOf("42.42+42.42i") complex64Value := reflect.ValueOf(complex64(0)) cases := []struct { f, t reflect.Value result interface{} err bool }{ {strValue, complex64Value, complex(float32(42.42), float32(42.42)), false}, {strValue, strValue, "42.42+42.42i", false}, {reflect.ValueOf(strings.Repeat("42", 420)), complex64Value, complex(float32(0), 0), true}, {reflect.ValueOf("42.42.42"), complex64Value, complex(float32(0), 0), true}, {reflect.ValueOf("-42.42"), complex64Value, complex(float32(-42.42), 0), false}, {reflect.ValueOf("0"), complex64Value, complex(float32(0), 0), false}, {reflect.ValueOf("0.0"), complex64Value, complex(float32(0), 0), false}, {reflect.ValueOf("1e3"), complex64Value, complex(float32(1000), 0), false}, {reflect.ValueOf("1e-3"), complex64Value, complex(float32(0.001), 0), false}, {reflect.ValueOf("1e3i"), complex64Value, complex(float32(0), 1000), false}, {reflect.ValueOf("1e-3i"), complex64Value, complex(float32(0), 0.001), false}, } for i, tc := range cases { f := StringToComplex64HookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, err) } if !tc.err && !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } func TestStringToComplex128HookFunc(t *testing.T) { strValue := reflect.ValueOf("42.42+42.42i") complex128Value := reflect.ValueOf(complex128(0)) cases := []struct { f, t reflect.Value result interface{} err bool }{ {strValue, complex128Value, complex(42.42, 42.42), false}, {strValue, strValue, "42.42+42.42i", false}, {reflect.ValueOf(strings.Repeat("42", 420)), complex128Value, complex(0, 0), true}, {reflect.ValueOf("42.42.42"), complex128Value, complex(0, 0), true}, {reflect.ValueOf("-42.42"), complex128Value, complex(-42.42, 0), false}, {reflect.ValueOf("0"), complex128Value, complex(0, 0), false}, {reflect.ValueOf("0.0"), complex128Value, complex(0, 0), false}, {reflect.ValueOf("1e3"), complex128Value, complex(1000, 0), false}, {reflect.ValueOf("1e-3"), complex128Value, complex(0.001, 0), false}, {reflect.ValueOf("1e3i"), complex128Value, complex(0, 1000), false}, {reflect.ValueOf("1e-3i"), complex128Value, complex(0, 0.001), false}, } for i, tc := range cases { f := StringToComplex128HookFunc() actual, err := DecodeHookExec(f, tc.f, tc.t) if tc.err != (err != nil) { t.Fatalf("case %d: expected err %#v", i, err) } if !tc.err && !reflect.DeepEqual(actual, tc.result) { t.Fatalf( "case %d: expected %#v, got %#v", i, tc.result, actual) } } } mapstructure-2.2.1/flake.lock000066400000000000000000000303531467411650400162220ustar00rootroot00000000000000{ "nodes": { "cachix": { "inputs": { "devenv": "devenv_2", "flake-compat": [ "devenv", "flake-compat" ], "nixpkgs": [ "devenv", "nixpkgs" ], "pre-commit-hooks": [ "devenv", "pre-commit-hooks" ] }, "locked": { "lastModified": 1712055811, "narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=", "owner": "cachix", "repo": "cachix", "rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30", "type": "github" }, "original": { "owner": "cachix", "repo": "cachix", "type": "github" } }, "devenv": { "inputs": { "cachix": "cachix", "flake-compat": "flake-compat_2", "nix": "nix_2", "nixpkgs": "nixpkgs_2", "pre-commit-hooks": "pre-commit-hooks" }, "locked": { "lastModified": 1717245169, "narHash": "sha256-+mW3rTBjGU8p1THJN0lX/Dd/8FbnF+3dB+mJuSaxewE=", "owner": "cachix", "repo": "devenv", "rev": "c3f9f053c077c6f88a3de5276d9178c62baa3fc3", "type": "github" }, "original": { "owner": "cachix", "repo": "devenv", "type": "github" } }, "devenv_2": { "inputs": { "flake-compat": [ "devenv", "cachix", "flake-compat" ], "nix": "nix", "nixpkgs": "nixpkgs", "poetry2nix": "poetry2nix", "pre-commit-hooks": [ "devenv", "cachix", "pre-commit-hooks" ] }, "locked": { "lastModified": 1708704632, "narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=", "owner": "cachix", "repo": "devenv", "rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196", "type": "github" }, "original": { "owner": "cachix", "ref": "python-rewrite", "repo": "devenv", "type": "github" } }, "flake-compat": { "flake": false, "locked": { "lastModified": 1673956053, "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", "owner": "edolstra", "repo": "flake-compat", "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", "type": "github" }, "original": { "owner": "edolstra", "repo": "flake-compat", "type": "github" } }, "flake-compat_2": { "flake": false, "locked": { "lastModified": 1696426674, "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", "owner": "edolstra", "repo": "flake-compat", "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "type": "github" }, "original": { "owner": "edolstra", "repo": "flake-compat", "type": "github" } }, "flake-parts": { "inputs": { "nixpkgs-lib": "nixpkgs-lib" }, "locked": { "lastModified": 1717285511, "narHash": "sha256-iKzJcpdXih14qYVcZ9QC9XuZYnPc6T8YImb6dX166kw=", "owner": "hercules-ci", "repo": "flake-parts", "rev": "2a55567fcf15b1b1c7ed712a2c6fadaec7412ea8", "type": "github" }, "original": { "owner": "hercules-ci", "repo": "flake-parts", "type": "github" } }, "flake-utils": { "inputs": { "systems": "systems" }, "locked": { "lastModified": 1689068808, "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", "owner": "numtide", "repo": "flake-utils", "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", "type": "github" }, "original": { "owner": "numtide", "repo": "flake-utils", "type": "github" } }, "flake-utils_2": { "inputs": { "systems": "systems_2" }, "locked": { "lastModified": 1710146030, "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { "owner": "numtide", "repo": "flake-utils", "type": "github" } }, "gitignore": { "inputs": { "nixpkgs": [ "devenv", "pre-commit-hooks", "nixpkgs" ] }, "locked": { "lastModified": 1709087332, "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", "owner": "hercules-ci", "repo": "gitignore.nix", "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", "type": "github" }, "original": { "owner": "hercules-ci", "repo": "gitignore.nix", "type": "github" } }, "nix": { "inputs": { "flake-compat": "flake-compat", "nixpkgs": [ "devenv", "cachix", "devenv", "nixpkgs" ], "nixpkgs-regression": "nixpkgs-regression" }, "locked": { "lastModified": 1712911606, "narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=", "owner": "domenkozar", "repo": "nix", "rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12", "type": "github" }, "original": { "owner": "domenkozar", "ref": "devenv-2.21", "repo": "nix", "type": "github" } }, "nix-github-actions": { "inputs": { "nixpkgs": [ "devenv", "cachix", "devenv", "poetry2nix", "nixpkgs" ] }, "locked": { "lastModified": 1688870561, "narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=", "owner": "nix-community", "repo": "nix-github-actions", "rev": "165b1650b753316aa7f1787f3005a8d2da0f5301", "type": "github" }, "original": { "owner": "nix-community", "repo": "nix-github-actions", "type": "github" } }, "nix_2": { "inputs": { "flake-compat": [ "devenv", "flake-compat" ], "nixpkgs": [ "devenv", "nixpkgs" ], "nixpkgs-regression": "nixpkgs-regression_2" }, "locked": { "lastModified": 1712911606, "narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=", "owner": "domenkozar", "repo": "nix", "rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12", "type": "github" }, "original": { "owner": "domenkozar", "ref": "devenv-2.21", "repo": "nix", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1692808169, "narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=", "owner": "NixOS", "repo": "nixpkgs", "rev": "9201b5ff357e781bf014d0330d18555695df7ba8", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixpkgs-unstable", "repo": "nixpkgs", "type": "github" } }, "nixpkgs-lib": { "locked": { "lastModified": 1717284937, "narHash": "sha256-lIbdfCsf8LMFloheeE6N31+BMIeixqyQWbSr2vk79EQ=", "type": "tarball", "url": "https://github.com/NixOS/nixpkgs/archive/eb9ceca17df2ea50a250b6b27f7bf6ab0186f198.tar.gz" }, "original": { "type": "tarball", "url": "https://github.com/NixOS/nixpkgs/archive/eb9ceca17df2ea50a250b6b27f7bf6ab0186f198.tar.gz" } }, "nixpkgs-regression": { "locked": { "lastModified": 1643052045, "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", "owner": "NixOS", "repo": "nixpkgs", "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", "type": "github" }, "original": { "owner": "NixOS", "repo": "nixpkgs", "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", "type": "github" } }, "nixpkgs-regression_2": { "locked": { "lastModified": 1643052045, "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", "owner": "NixOS", "repo": "nixpkgs", "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", "type": "github" }, "original": { "owner": "NixOS", "repo": "nixpkgs", "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", "type": "github" } }, "nixpkgs-stable": { "locked": { "lastModified": 1710695816, "narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=", "owner": "NixOS", "repo": "nixpkgs", "rev": "614b4613980a522ba49f0d194531beddbb7220d3", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixos-23.11", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_2": { "locked": { "lastModified": 1713361204, "narHash": "sha256-TA6EDunWTkc5FvDCqU3W2T3SFn0gRZqh6D/hJnM02MM=", "owner": "cachix", "repo": "devenv-nixpkgs", "rev": "285676e87ad9f0ca23d8714a6ab61e7e027020c6", "type": "github" }, "original": { "owner": "cachix", "ref": "rolling", "repo": "devenv-nixpkgs", "type": "github" } }, "nixpkgs_3": { "locked": { "lastModified": 1717112898, "narHash": "sha256-7R2ZvOnvd9h8fDd65p0JnB7wXfUvreox3xFdYWd1BnY=", "owner": "NixOS", "repo": "nixpkgs", "rev": "6132b0f6e344ce2fe34fc051b72fb46e34f668e0", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixpkgs-unstable", "repo": "nixpkgs", "type": "github" } }, "poetry2nix": { "inputs": { "flake-utils": "flake-utils", "nix-github-actions": "nix-github-actions", "nixpkgs": [ "devenv", "cachix", "devenv", "nixpkgs" ] }, "locked": { "lastModified": 1692876271, "narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=", "owner": "nix-community", "repo": "poetry2nix", "rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3", "type": "github" }, "original": { "owner": "nix-community", "repo": "poetry2nix", "type": "github" } }, "pre-commit-hooks": { "inputs": { "flake-compat": [ "devenv", "flake-compat" ], "flake-utils": "flake-utils_2", "gitignore": "gitignore", "nixpkgs": [ "devenv", "nixpkgs" ], "nixpkgs-stable": "nixpkgs-stable" }, "locked": { "lastModified": 1713775815, "narHash": "sha256-Wu9cdYTnGQQwtT20QQMg7jzkANKQjwBD9iccfGKkfls=", "owner": "cachix", "repo": "pre-commit-hooks.nix", "rev": "2ac4dcbf55ed43f3be0bae15e181f08a57af24a4", "type": "github" }, "original": { "owner": "cachix", "repo": "pre-commit-hooks.nix", "type": "github" } }, "root": { "inputs": { "devenv": "devenv", "flake-parts": "flake-parts", "nixpkgs": "nixpkgs_3" } }, "systems": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", "owner": "nix-systems", "repo": "default", "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", "type": "github" }, "original": { "owner": "nix-systems", "repo": "default", "type": "github" } }, "systems_2": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", "owner": "nix-systems", "repo": "default", "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", "type": "github" }, "original": { "owner": "nix-systems", "repo": "default", "type": "github" } } }, "root": "root", "version": 7 } mapstructure-2.2.1/flake.nix000066400000000000000000000017151467411650400160700ustar00rootroot00000000000000{ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; flake-parts.url = "github:hercules-ci/flake-parts"; devenv.url = "github:cachix/devenv"; }; outputs = inputs@{ flake-parts, ... }: flake-parts.lib.mkFlake { inherit inputs; } { imports = [ inputs.devenv.flakeModule ]; systems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ]; perSystem = { config, self', inputs', pkgs, system, ... }: rec { devenv.shells = { default = { languages = { go.enable = true; }; pre-commit.hooks = { nixpkgs-fmt.enable = true; }; packages = with pkgs; [ golangci-lint ]; # https://github.com/cachix/devenv/issues/528#issuecomment-1556108767 containers = pkgs.lib.mkForce { }; }; ci = devenv.shells.default; }; }; }; } mapstructure-2.2.1/go.mod000066400000000000000000000000641467411650400153700ustar00rootroot00000000000000module github.com/go-viper/mapstructure/v2 go 1.18 mapstructure-2.2.1/internal/000077500000000000000000000000001467411650400160765ustar00rootroot00000000000000mapstructure-2.2.1/internal/errors/000077500000000000000000000000001467411650400174125ustar00rootroot00000000000000mapstructure-2.2.1/internal/errors/errors.go000066400000000000000000000002521467411650400212540ustar00rootroot00000000000000package errors import "errors" func New(text string) error { return errors.New(text) } func As(err error, target interface{}) bool { return errors.As(err, target) } mapstructure-2.2.1/internal/errors/join.go000066400000000000000000000001641467411650400207010ustar00rootroot00000000000000//go:build go1.20 package errors import "errors" func Join(errs ...error) error { return errors.Join(errs...) } mapstructure-2.2.1/internal/errors/join_go1_19.go000066400000000000000000000025251467411650400217630ustar00rootroot00000000000000//go:build !go1.20 // Copyright 2022 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package errors // Join returns an error that wraps the given errors. // Any nil error values are discarded. // Join returns nil if every value in errs is nil. // The error formats as the concatenation of the strings obtained // by calling the Error method of each element of errs, with a newline // between each string. // // A non-nil error returned by Join implements the Unwrap() []error method. func Join(errs ...error) error { n := 0 for _, err := range errs { if err != nil { n++ } } if n == 0 { return nil } e := &joinError{ errs: make([]error, 0, n), } for _, err := range errs { if err != nil { e.errs = append(e.errs, err) } } return e } type joinError struct { errs []error } func (e *joinError) Error() string { // Since Join returns nil if every value in errs is nil, // e.errs cannot be empty. if len(e.errs) == 1 { return e.errs[0].Error() } b := []byte(e.errs[0].Error()) for _, err := range e.errs[1:] { b = append(b, '\n') b = append(b, err.Error()...) } // At this point, b has at least one byte '\n'. // return unsafe.String(&b[0], len(b)) return string(b) } func (e *joinError) Unwrap() []error { return e.errs } mapstructure-2.2.1/mapstructure.go000066400000000000000000001344151467411650400173570ustar00rootroot00000000000000// Package mapstructure exposes functionality to convert one arbitrary // Go type into another, typically to convert a 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. // // The simplest function to start with is Decode. // // # Field Tags // // When decoding to a struct, mapstructure will use the field name by // default to perform the mapping. For example, if a struct has a field // "Username" then mapstructure will look for a key in the source value // of "username" (case insensitive). // // type User struct { // Username string // } // // You can change the behavior of mapstructure by using struct tags. // The default struct tag that mapstructure looks for is "mapstructure" // but you can customize it using DecoderConfig. // // # Renaming Fields // // To rename the key that mapstructure looks for, use the "mapstructure" // tag and set a value directly. For example, to change the "username" example // above to "user": // // type User struct { // Username string `mapstructure:"user"` // } // // # Embedded Structs and Squashing // // Embedded structs are treated as if they're another field with that name. // By default, the two structs below are equivalent when decoding with // mapstructure: // // type Person struct { // Name string // } // // type Friend struct { // Person // } // // type Friend struct { // Person Person // } // // This would require an input that looks like below: // // map[string]interface{}{ // "person": map[string]interface{}{"name": "alice"}, // } // // If your "person" value is NOT nested, then you can append ",squash" to // your tag value and mapstructure will treat it as if the embedded struct // were part of the struct directly. Example: // // type Friend struct { // Person `mapstructure:",squash"` // } // // Now the following input would be accepted: // // map[string]interface{}{ // "name": "alice", // } // // When decoding from a struct to a map, the squash tag squashes the struct // fields into a single map. Using the example structs from above: // // Friend{Person: Person{Name: "alice"}} // // Will be decoded into a map: // // map[string]interface{}{ // "name": "alice", // } // // DecoderConfig has a field that changes the behavior of mapstructure // to always squash embedded structs. // // # Remainder Values // // If there are any unmapped keys in the source value, mapstructure by // default will silently ignore them. You can error by setting ErrorUnused // in DecoderConfig. If you're using Metadata you can also maintain a slice // of the unused keys. // // You can also use the ",remain" suffix on your tag to collect all unused // values in a map. The field with this tag MUST be a map type and should // probably be a "map[string]interface{}" or "map[interface{}]interface{}". // See example below: // // type Friend struct { // Name string // Other map[string]interface{} `mapstructure:",remain"` // } // // Given the input below, Other would be populated with the other // values that weren't used (everything but "name"): // // map[string]interface{}{ // "name": "bob", // "address": "123 Maple St.", // } // // # Omit Empty Values // // When decoding from a struct to any other value, you may use the // ",omitempty" suffix on your tag to omit that value if it equates to // the zero value. The zero value of all types is specified in the Go // specification. // // For example, the zero type of a numeric type is zero ("0"). If the struct // field value is zero and a numeric type, the field is empty, and it won't // be encoded into the destination type. // // type Source struct { // Age int `mapstructure:",omitempty"` // } // // # Unexported fields // // Since unexported (private) struct fields cannot be set outside the package // where they are defined, the decoder will simply skip them. // // For this output type definition: // // type Exported struct { // private string // this unexported field will be skipped // Public string // } // // Using this map as input: // // map[string]interface{}{ // "private": "I will be ignored", // "Public": "I made it through!", // } // // The following struct will be decoded: // // type Exported struct { // private: "" // field is left with an empty string (zero value) // Public: "I made it through!" // } // // # Other Configuration // // mapstructure is highly configurable. See the DecoderConfig struct // for other features and options that are supported. package mapstructure import ( "encoding/json" "fmt" "reflect" "sort" "strconv" "strings" "github.com/go-viper/mapstructure/v2/internal/errors" ) // DecodeHookFunc is the callback function that can be used for // data transformations. See "DecodeHook" in the DecoderConfig // struct. // // The type must be one of DecodeHookFuncType, DecodeHookFuncKind, or // DecodeHookFuncValue. // Values are a superset of Types (Values can return types), and 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) // DecodeHookFuncValue is a DecodeHookFunc which has complete access to both the source and target // values. type DecodeHookFuncValue func(from reflect.Value, to reflect.Value) (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. The // DecodeHook is called for every map and value in the input. This means // that if a struct has embedded fields with squash tags the decode hook // is called only once with all of the input data, not once for each // embedded 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 // If ErrorUnset is true, then it is an error for there to exist // fields in the result that were not set in the decoding process // (extra fields). This only applies to decoding to a struct. This // will affect all nested structs as well. ErrorUnset 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 // Squash will squash embedded structs. A squash tag may also be // added to an individual struct field using a tag. For example: // // type Parent struct { // Child `mapstructure:",squash"` // } Squash 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 // The option of the value in the tag that indicates a field should // be squashed. This defaults to "squash". SquashTagOption string // IgnoreUntaggedFields ignores all struct fields without explicit // TagName, comparable to `mapstructure:"-"` as default behaviour. IgnoreUntaggedFields bool // MatchName is the function used to match the map key to the struct // field name or tag. Defaults to `strings.EqualFold`. This can be used // to implement case-sensitive tag values, support snake casing, etc. MatchName func(mapKey, fieldName string) bool // DecodeNil, if set to true, will cause the DecodeHook (if present) to run // even if the input is nil. This can be used to provide default values. DecodeNil bool } // 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 cachedDecodeHook func(from reflect.Value, to reflect.Value) (interface{}, error) } // 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 // Unset is a slice of field names that were found in the result interface // but weren't set in the decoding process since there was no matching value // in the input Unset []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.Metadata.Unset == nil { config.Metadata.Unset = make([]string, 0) } } if config.TagName == "" { config.TagName = "mapstructure" } if config.SquashTagOption == "" { config.SquashTagOption = "squash" } if config.MatchName == nil { config.MatchName = strings.EqualFold } result := &Decoder{ config: config, } if config.DecodeHook != nil { result.cachedDecodeHook = cachedDecodeHook(config.DecodeHook) } return result, nil } // Decode decodes the given raw interface to the target pointer specified // by the configuration. func (d *Decoder) Decode(input interface{}) error { err := d.decode("", input, reflect.ValueOf(d.config.Result).Elem()) // Retain some of the original behavior when multiple errors ocurr var joinedErr interface{ Unwrap() []error } if errors.As(err, &joinedErr) { return fmt.Errorf("decoding failed due to the following error(s):\n\n%w", err) } return err } // isNil returns true if the input is nil or a typed nil pointer. func isNil(input interface{}) bool { if input == nil { return true } val := reflect.ValueOf(input) return val.Kind() == reflect.Ptr && val.IsNil() } // 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.ValueOf(input) outputKind = getKind(outVal) decodeNil = d.config.DecodeNil && d.cachedDecodeHook != nil ) if isNil(input) { // Typed nils won't match the "input == nil" below, so reset input. 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) } } if !decodeNil { return nil } } if !inputVal.IsValid() { if !decodeNil { // 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 } // Hooks need a valid inputVal, so reset it to zero value of outVal type. switch outputKind { case reflect.Struct, reflect.Map: var mapVal map[string]interface{} inputVal = reflect.ValueOf(mapVal) // create nil map pointer case reflect.Slice, reflect.Array: var sliceVal []interface{} inputVal = reflect.ValueOf(sliceVal) // create nil slice pointer default: inputVal = reflect.Zero(outVal.Type()) } } if d.cachedDecodeHook != nil { // We have a DecodeHook, so let's pre-process the input. var err error input, err = d.cachedDecodeHook(inputVal, outVal) if err != nil { return fmt.Errorf("error decoding '%s': %w", name, err) } } if isNil(input) { return nil } var err error addMetaKey := true 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.Complex64: err = d.decodeComplex(name, input, outVal) case reflect.Struct: err = d.decodeStruct(name, input, outVal) case reflect.Map: err = d.decodeMap(name, input, outVal) case reflect.Ptr: addMetaKey, 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 addMetaKey && 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() { elem := val.Elem() // If we can't address this element, then its not writable. Instead, // we make a copy of the value (which is a pointer and therefore // writable), decode into that, and replace the whole value. copied := false if !elem.CanAddr() { copied = true // Make *T copy := reflect.New(elem.Type()) // *T = elem copy.Elem().Set(elem) // Set elem so we decode into it elem = copy } // Decode. If we have an error then return. We also return right // away if we're not a copy because that means we decoded directly. if err := d.decode(name, data, elem); err != nil || !copied { return err } // If we're a copy, we need to set te final result val.Set(elem.Elem()) return nil } 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', value: '%v'", name, val.Type(), dataVal.Type(), data) } 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: str := dataVal.String() if str == "" { str = "0" } i, err := strconv.ParseInt(str, 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', value: '%v'", name, val.Type(), dataVal.Type(), data) } return nil } func (d *Decoder) decodeUint(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: 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: str := dataVal.String() if str == "" { str = "0" } i, err := strconv.ParseUint(str, 0, val.Type().Bits()) if err == nil { val.SetUint(i) } else { return fmt.Errorf("cannot parse '%s' as uint: %s", name, err) } case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": jn := data.(json.Number) i, err := strconv.ParseUint(string(jn), 0, 64) if err != nil { return fmt.Errorf( "error decoding json.Number into %s: %s", name, err) } val.SetUint(i) default: return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", name, val.Type(), dataVal.Type(), data) } 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 '%#v', value: '%#v'", name, val, dataVal, data) } 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: str := dataVal.String() if str == "" { str = "0" } f, err := strconv.ParseFloat(str, 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', value: '%v'", name, val.Type(), dataVal.Type(), data) } return nil } func (d *Decoder) decodeComplex(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) switch { case dataKind == reflect.Complex64: val.SetComplex(dataVal.Complex()) default: return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", name, val.Type(), dataVal.Type(), data) } 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) } dataVal := reflect.ValueOf(data) // Resolve any levels of indirection for dataVal.Kind() == reflect.Pointer { dataVal = reflect.Indirect(dataVal) } // Check input type and based on the input type jump to the proper func 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( name+"["+strconv.Itoa(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 var errs []error // 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 := name + "[" + k.String() + "]" // First decode the key into the proper type currentKey := reflect.Indirect(reflect.New(valKeyType)) if err := d.decode(fieldName, k.Interface(), currentKey); err != nil { errs = append(errs, 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 { errs = append(errs, err) continue } valMap.SetMapIndex(currentKey, currentVal) } // Set the built up map to the value val.Set(valMap) return errors.Join(errs...) } 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) keyName := f.Name if tagValue == "" && d.config.IgnoreUntaggedFields { continue } // If Squash is set in the config, we squash the field down. squash := d.config.Squash && v.Kind() == reflect.Struct && f.Anonymous v = dereferencePtrToStructIfNeeded(v, d.config.TagName) // Determine the name of the key in the map if index := strings.Index(tagValue, ","); index != -1 { if tagValue[:index] == "-" { continue } // If "omitempty" is specified in the tag, it ignores empty values. if strings.Index(tagValue[index+1:], "omitempty") != -1 && isEmptyValue(v) { continue } // If "squash" is specified in the tag, we squash the field down. squash = squash || strings.Contains(tagValue[index+1:], d.config.SquashTagOption) if squash { // When squashing, the embedded type can be a pointer to a struct. if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct { v = v.Elem() } // The final type must be a struct if v.Kind() != reflect.Struct { return fmt.Errorf("cannot squash non-struct type '%s'", v.Type()) } } else { if strings.Index(tagValue[index+1:], "remain") != -1 { if v.Kind() != reflect.Map { return fmt.Errorf("error remain-tag field with invalid type: '%s'", v.Type()) } ptr := v.MapRange() for ptr.Next() { valMap.SetMapIndex(ptr.Key(), ptr.Value()) } continue } } if keyNameTagValue := tagValue[:index]; keyNameTagValue != "" { keyName = keyNameTagValue } } else if len(tagValue) > 0 { if tagValue == "-" { continue } keyName = tagValue } 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) // Creating a pointer to a map so that other methods can completely // overwrite the map if need be (looking at you decodeMapFromMap). The // indirection allows the underlying map to be settable (CanSet() == true) // where as reflect.MakeMap returns an unsettable map. addrVal := reflect.New(vMap.Type()) reflect.Indirect(addrVal).Set(vMap) err := d.decode(keyName, x.Interface(), reflect.Indirect(addrVal)) if err != nil { return err } // the underlying map may have been completely overwritten so pull // it indirectly out of the enclosing value. vMap = reflect.Indirect(addrVal) 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) (bool, 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 true, 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 false, err } val.Set(realVal) } else { if err := d.decode(name, data, reflect.Indirect(val)); err != nil { return false, err } } return false, 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', value: '%v'", name, val.Type(), dataVal.Type(), data) } 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) // If we have a non array/slice type then we first attempt to convert. if dataValKind != reflect.Array && dataValKind != reflect.Slice { 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) } } return fmt.Errorf( "'%s': source data must be an array or slice, got %s", name, dataValKind) } // If the input value is nil, then don't allocate since empty != nil if dataValKind != reflect.Array && dataVal.IsNil() { return nil } valSlice := val if valSlice.IsNil() || d.config.ZeroFields { // Make a new slice to hold our result, same size as the original data. valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len()) } else if valSlice.Len() > dataVal.Len() { valSlice = valSlice.Slice(0, dataVal.Len()) } // Accumulate any errors var errs []error 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 := name + "[" + strconv.Itoa(i) + "]" if err := d.decode(fieldName, currentData, currentField); err != nil { errs = append(errs, err) } } // Finally, set the value to the slice we built up val.Set(valSlice) return errors.Join(errs...) } 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 isComparable(valArray) && 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 var errs []error for i := 0; i < dataVal.Len(); i++ { currentData := dataVal.Index(i).Interface() currentField := valArray.Index(i) fieldName := name + "[" + strconv.Itoa(i) + "]" if err := d.decode(fieldName, currentData, currentField); err != nil { errs = append(errs, err) } } // Finally, set the value to the array we built up val.Set(valArray) return errors.Join(errs...) } 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. // Make a new map to hold our result mapType := reflect.TypeOf((map[string]interface{})(nil)) mval := reflect.MakeMap(mapType) // Creating a pointer to a map so that other methods can completely // overwrite the map if need be (looking at you decodeMapFromMap). The // indirection allows the underlying map to be settable (CanSet() == true) // where as reflect.MakeMap returns an unsettable map. addrVal := reflect.New(mval.Type()) reflect.Indirect(addrVal).Set(mval) if err := d.decodeMapFromStruct(name, dataVal, reflect.Indirect(addrVal), mval); err != nil { return err } result := d.decodeStructFromMap(name, reflect.Indirect(addrVal), 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{}{} } targetValKeysUnused := make(map[interface{}]struct{}) var errs []error // 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 } // remainField is set to a valid field set with the "remain" tag if // we are keeping track of remaining values. var remainField *field 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) fieldVal := structVal.Field(i) if fieldVal.Kind() == reflect.Ptr && fieldVal.Elem().Kind() == reflect.Struct { // Handle embedded struct pointers as embedded structs. fieldVal = fieldVal.Elem() } // If "squash" is specified in the tag, we squash the field down. squash := d.config.Squash && fieldVal.Kind() == reflect.Struct && fieldType.Anonymous remain := false // We always parse the tags cause we're looking for other tags too tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",") for _, tag := range tagParts[1:] { if tag == d.config.SquashTagOption { squash = true break } if tag == "remain" { remain = true break } } if squash { switch fieldVal.Kind() { case reflect.Struct: structs = append(structs, fieldVal) case reflect.Interface: if !fieldVal.IsNil() { structs = append(structs, fieldVal.Elem().Elem()) } default: errs = append(errs, fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind())) } continue } // Build our field if remain { remainField = &field{fieldType, fieldVal} } else { // Normal struct field, store it away fields = append(fields, field{fieldType, fieldVal}) } } } // 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) if tagValue == "" && d.config.IgnoreUntaggedFields { continue } 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 d.config.MatchName(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. Remember it for potential errors and metadata. targetValKeysUnused[fieldName] = struct{}{} continue } } 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 } // Delete the key we're using from the unused map so we stop tracking delete(dataValKeysUnused, rawMapKey.Interface()) // If the name is empty string, then we're at the root, and we // don't dot-join the fields. if name != "" { fieldName = name + "." + fieldName } if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil { errs = append(errs, err) } } // If we have a "remain"-tagged field and we have unused keys then // we put the unused keys directly into the remain field. if remainField != nil && len(dataValKeysUnused) > 0 { // Build a map of only the unused values remain := map[interface{}]interface{}{} for key := range dataValKeysUnused { remain[key] = dataVal.MapIndex(reflect.ValueOf(key)).Interface() } // Decode it as-if we were just decoding this map onto our map. if err := d.decodeMap(name, remain, remainField.val); err != nil { errs = append(errs, err) } // Set the map to nil so we have none so that the next check will // not error (ErrorUnused) dataValKeysUnused = nil } 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, ", ")) errs = append(errs, err) } if d.config.ErrorUnset && len(targetValKeysUnused) > 0 { keys := make([]string, 0, len(targetValKeysUnused)) for rawKey := range targetValKeysUnused { keys = append(keys, rawKey.(string)) } sort.Strings(keys) err := fmt.Errorf("'%s' has unset fields: %s", name, strings.Join(keys, ", ")) errs = append(errs, err) } if err := errors.Join(errs...); err != nil { return err } // 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 = name + "." + key } d.config.Metadata.Unused = append(d.config.Metadata.Unused, key) } for rawKey := range targetValKeysUnused { key := rawKey.(string) if name != "" { key = name + "." + key } d.config.Metadata.Unset = append(d.config.Metadata.Unset, key) } } return nil } func isEmptyValue(v reflect.Value) bool { switch getKind(v) { case reflect.Array, reflect.Map, reflect.Slice, reflect.String: return v.Len() == 0 case reflect.Bool: return !v.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint() == 0 case reflect.Float32, reflect.Float64: return v.Float() == 0 case reflect.Interface, reflect.Ptr: return v.IsNil() } return false } 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 case kind >= reflect.Complex64 && kind <= reflect.Complex128: return reflect.Complex64 default: return kind } } func isStructTypeConvertibleToMap(typ reflect.Type, checkMapstructureTags bool, tagName string) bool { for i := 0; i < typ.NumField(); i++ { f := typ.Field(i) if f.PkgPath == "" && !checkMapstructureTags { // check for unexported fields return true } if checkMapstructureTags && f.Tag.Get(tagName) != "" { // check for mapstructure tags inside return true } } return false } func dereferencePtrToStructIfNeeded(v reflect.Value, tagName string) reflect.Value { if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { return v } deref := v.Elem() derefT := deref.Type() if isStructTypeConvertibleToMap(derefT, true, tagName) { return deref } return v } mapstructure-2.2.1/mapstructure_benchmark_test.go000066400000000000000000000141721467411650400224250ustar00rootroot00000000000000package mapstructure import ( "encoding/json" "testing" ) type Person struct { Name string Age int Emails []string Extra map[string]string } func Benchmark_Decode(b *testing.B) { 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) { 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_JSONUnmarshal(b *testing.B) { input := map[string]interface{}{ "name": "Mitchell", "age": 91, "emails": []string{"one", "two", "three"}, "extra": map[string]string{ "twitter": "mitchellh", }, } inputB, err := json.Marshal(input) if err != nil { b.Fatal("Failed to marshal test input:", err) } var result Person for i := 0; i < b.N; i++ { json.Unmarshal(inputB, &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, "vjsonInt": json.Number("1234"), "vjsonFloat": json.Number("1234.5"), "vjsonNumber": json.Number("1234.5"), } for i := 0; i < b.N; i++ { var result Basic 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) { // 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) { 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) } } func Benchmark_DecodeWithRemainingFields(b *testing.B) { type Person struct { Name string Other map[string]interface{} `mapstructure:",remain"` } input := map[string]interface{}{ "name": "Luffy", "age": 19, "powers": []string{ "Rubber Man", "Conqueror Haki", }, } for i := 0; i < b.N; i++ { // Decoding Map -> Struct var person Person _ = Decode(input, &person) // Decoding Struct -> Map result := make(map[string]interface{}) _ = Decode(&person, &result) } } mapstructure-2.2.1/mapstructure_bugs_test.go000066400000000000000000000365401467411650400214360ustar00rootroot00000000000000package mapstructure import ( "reflect" "testing" "time" ) // 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) } } } // #103 Check for data type before trying to access its composants prevent a panic error // in decodeSlice func TestDecodeBadDataTypeInSlice(t *testing.T) { t.Parallel() input := map[string]interface{}{ "Toto": "titi", } result := []struct { Toto string }{} if err := Decode(input, &result); err == nil { t.Error("An error was expected, got nil") } } // #202 Ensure that intermediate maps in the struct -> struct decode process are settable // and not just the elements within them. func TestDecodeIntermediateMapsSettable(t *testing.T) { type Timestamp struct { Seconds int64 Nanos int32 } type TsWrapper struct { Timestamp *Timestamp } type TimeWrapper struct { Timestamp time.Time } input := TimeWrapper{ Timestamp: time.Unix(123456789, 987654), } expected := TsWrapper{ Timestamp: &Timestamp{ Seconds: 123456789, Nanos: 987654, }, } timePtrType := reflect.TypeOf((*time.Time)(nil)) mapStrInfType := reflect.TypeOf((map[string]interface{})(nil)) var actual TsWrapper decoder, err := NewDecoder(&DecoderConfig{ Result: &actual, DecodeHook: func(from, to reflect.Type, data interface{}) (interface{}, error) { if from == timePtrType && to == mapStrInfType { ts := data.(*time.Time) nanos := ts.UnixNano() seconds := nanos / 1000000000 nanos = nanos % 1000000000 return &map[string]interface{}{ "Seconds": seconds, "Nanos": int32(nanos), }, nil } return data, nil }, }) if err != nil { t.Fatalf("failed to create decoder: %v", err) } if err := decoder.Decode(&input); err != nil { t.Fatalf("failed to decode input: %v", err) } if !reflect.DeepEqual(expected, actual) { t.Fatalf("expected: %#[1]v (%[1]T), got: %#[2]v (%[2]T)", expected, actual) } } // GH-206: decodeInt throws an error for an empty string func TestDecode_weakEmptyStringToInt(t *testing.T) { input := map[string]interface{}{ "StringToInt": "", "StringToUint": "", "StringToBool": "", "StringToFloat": "", } expectedResultWeak := TypeConversionResult{ StringToInt: 0, StringToUint: 0, StringToBool: false, StringToFloat: 0, } // Test weak type conversion var resultWeak TypeConversionResult err := WeakDecode(input, &resultWeak) 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) } } // GH-228: Squash cause *time.Time set to zero func TestMapSquash(t *testing.T) { type AA struct { T *time.Time } type A struct { AA } v := time.Now() in := &AA{ T: &v, } out := &A{} d, err := NewDecoder(&DecoderConfig{ Squash: true, Result: out, }) if err != nil { t.Fatalf("err: %s", err) } if err := d.Decode(in); err != nil { t.Fatalf("err: %s", err) } // these failed if !v.Equal(*out.T) { t.Fatal("expected equal") } if out.T.IsZero() { t.Fatal("expected false") } } // GH-238: Empty key name when decoding map from struct with only omitempty flag func TestMapOmitEmptyWithEmptyFieldnameInTag(t *testing.T) { type Struct struct { Username string `mapstructure:",omitempty"` Age int `mapstructure:",omitempty"` } s := Struct{ Username: "Joe", } var m map[string]interface{} if err := Decode(s, &m); err != nil { t.Fatal(err) } if len(m) != 1 { t.Fatalf("fail: %#v", m) } if m["Username"] != "Joe" { t.Fatalf("fail: %#v", m) } } // GH-340: Decoding array of slices causes panic type HasNonComparableType struct { NonComparableType [2][]byte } func TestDecode_nonComparableType(t *testing.T) { decodeTo := &HasNonComparableType{} expected := [2][]byte{{1, 2}, {3, 4, 5}} if err := Decode(map[string]interface{}{"NonComparableType": expected}, &decodeTo); err != nil { t.Fatal(err) } if !reflect.DeepEqual(expected, decodeTo.NonComparableType) { t.Fatalf("fail: %#v", decodeTo.NonComparableType) } } // GH-347: Decoding maps with multiple indirection results in an error func TestDecodeToMapWithMultipleIndirection(t *testing.T) { t.Run("Struct", func(t *testing.T) { type Struct struct { Foo string `mapstructure:"foo"` } v := Struct{ Foo: "bar", } i := &v ii := &i iii := &ii var actual map[string]interface{} if err := Decode(iii, &actual); err != nil { t.Fatal(err) } expected := map[string]interface{}{ "foo": "bar", } if !reflect.DeepEqual(actual, expected) { t.Fatalf("expected: %#v, got: %#v", expected, actual) } }) t.Run("Map", func(t *testing.T) { v := map[string]interface{}{ "foo": "bar", } i := &v ii := &i iii := &ii var actual map[string]interface{} if err := Decode(iii, &actual); err != nil { t.Fatal(err) } expected := map[string]interface{}{ "foo": "bar", } if !reflect.DeepEqual(actual, expected) { t.Fatalf("expected: %#v, got: %#v", expected, actual) } }) t.Run("Array", func(t *testing.T) { v := [1]map[string]interface{}{ { "foo": "bar", }, } i := &v ii := &i iii := &ii var actual map[string]interface{} if err := WeakDecode(iii, &actual); err != nil { t.Fatal(err) } expected := map[string]interface{}{ "foo": "bar", } if !reflect.DeepEqual(actual, expected) { t.Fatalf("expected: %#v, got: %#v", expected, actual) } }) t.Run("Slice", func(t *testing.T) { v := []map[string]interface{}{ { "foo": "bar", }, } i := &v ii := &i iii := &ii var actual map[string]interface{} if err := WeakDecode(iii, &actual); err != nil { t.Fatal(err) } expected := map[string]interface{}{ "foo": "bar", } if !reflect.DeepEqual(actual, expected) { t.Fatalf("expected: %#v, got: %#v", expected, actual) } }) } mapstructure-2.2.1/mapstructure_examples_test.go000066400000000000000000000202551467411650400223100ustar00rootroot00000000000000package mapstructure import ( "fmt" "reflect" "strconv" "strings" ) 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: // decoding failed due to the following error(s): // // 'Name' expected type 'string', got unconvertible type 'int', value: '123' // 'Age' expected type 'int', got unconvertible type 'string', value: 'bad value' // 'Emails[0]' expected type 'string', got unconvertible type 'int', value: '1' // 'Emails[1]' expected type 'string', got unconvertible type 'int', value: '2' // 'Emails[2]' expected type 'string', got unconvertible type 'int', value: '3' } 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 } func ExampleDecode_remainingData() { // Note that the mapstructure tags defined in the struct type // can indicate which fields the values are mapped to. type Person struct { Name string Age int Other map[string]interface{} `mapstructure:",remain"` } input := map[string]interface{}{ "name": "Mitchell", "age": 91, "email": "mitchell@example.com", } var result Person err := Decode(input, &result) if err != nil { panic(err) } fmt.Printf("%#v", result) // Output: // mapstructure.Person{Name:"Mitchell", Age:91, Other:map[string]interface {}{"email":"mitchell@example.com"}} } func ExampleDecode_remainingDataDecodeBackToMapInFlatFormat() { // Note that the mapstructure tags defined in the struct type // can indicate which fields the values are mapped to. type Person struct { Name string Age int Other map[string]interface{} `mapstructure:",remain"` } input := map[string]interface{}{ "name": "Luffy", "age": 19, "powers": []string{ "Rubber Man", "Conqueror Haki", }, } var person Person err := Decode(input, &person) if err != nil { panic(err) } result := make(map[string]interface{}) err = Decode(&person, &result) if err != nil { panic(err) } fmt.Printf("%#v", result) // Output: // map[string]interface {}{"Age":19, "Name":"Luffy", "powers":[]string{"Rubber Man", "Conqueror Haki"}} } func ExampleDecode_omitempty() { // Add omitempty annotation to avoid map keys for empty values type Family struct { LastName string } type Location struct { City string } type Person struct { *Family `mapstructure:",omitempty"` *Location `mapstructure:",omitempty"` Age int FirstName string } result := &map[string]interface{}{} input := Person{FirstName: "Somebody"} err := Decode(input, &result) if err != nil { panic(err) } fmt.Printf("%+v", result) // Output: // &map[Age:0 FirstName:Somebody] } func ExampleDecode_decodeHookFunc() { type PersonLocation struct { Latitude float64 Longtitude float64 } type Person struct { Name string Location PersonLocation } // Example of parsing messy input: here we have latitude, longitude squashed into // a single string field. We write a custom DecodeHookFunc to parse the '#' separated // values into a PersonLocation struct. input := map[string]interface{}{ "name": "Mitchell", "location": "-35.2809#149.1300", } toPersonLocationHookFunc := func() DecodeHookFunc { return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if t != reflect.TypeOf(PersonLocation{}) { return data, nil } switch f.Kind() { case reflect.String: xs := strings.Split(data.(string), "#") if len(xs) == 2 { lat, errLat := strconv.ParseFloat(xs[0], 64) lon, errLon := strconv.ParseFloat(xs[1], 64) if errLat == nil && errLon == nil { return PersonLocation{Latitude: lat, Longtitude: lon}, nil } } else { return data, nil } } return data, nil } } var result Person decoder, errDecoder := NewDecoder(&DecoderConfig{ Metadata: nil, DecodeHook: toPersonLocationHookFunc(), // Here, use ComposeDecodeHookFunc to run multiple hooks. Result: &result, }) if errDecoder != nil { panic(errDecoder) } err := decoder.Decode(input) if err != nil { panic(err) } fmt.Printf("%#v", result) // Output: // mapstructure.Person{Name:"Mitchell", Location:mapstructure.PersonLocation{Latitude:-35.2809, Longtitude:149.13}} } mapstructure-2.2.1/mapstructure_test.go000066400000000000000000002126441467411650400204170ustar00rootroot00000000000000package mapstructure import ( "encoding/json" "errors" "io" "reflect" "sort" "strings" "testing" "time" ) type Basic struct { Vstring string Vint int Vint8 int8 Vint16 int16 Vint32 int32 Vint64 int64 Vuint uint Vbool bool Vfloat float64 Vextra string vsilent bool Vdata interface{} VjsonInt int VjsonUint uint VjsonUint64 uint64 VjsonFloat float64 VjsonNumber json.Number Vcomplex64 complex64 Vcomplex128 complex128 } 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 BasicJSONInline struct { Test Basic `json:",inline"` } type Embedded struct { Basic Vunique string } type EmbeddedPointer struct { *Basic Vunique string } type EmbeddedSquash struct { Basic `mapstructure:",squash"` Vunique string } type EmbeddedPointerSquash struct { *Basic `mapstructure:",squash"` Vunique string } type BasicMapStructure struct { Vunique string `mapstructure:"vunique"` Vtime *time.Time `mapstructure:"time"` } type NestedPointerWithMapstructure struct { Vbar *BasicMapStructure `mapstructure:"vbar"` } type EmbeddedPointerSquashWithNestedMapstructure struct { *NestedPointerWithMapstructure `mapstructure:",squash"` Vunique string } type EmbeddedAndNamed struct { Basic Named Basic 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 TestInterface interface { GetVfoo() string GetVbarfoo() string GetVfoobar() string } type TestInterfaceImpl struct { Vfoo string } func (t *TestInterfaceImpl) GetVfoo() string { return t.Vfoo } func (t *TestInterfaceImpl) GetVbarfoo() string { return "" } func (t *TestInterfaceImpl) GetVfoobar() string { return "" } type TestNestedInterfaceImpl struct { SquashOnNestedInterfaceType `mapstructure:",squash"` Vfoo string } func (t *TestNestedInterfaceImpl) GetVfoo() string { return t.Vfoo } func (t *TestNestedInterfaceImpl) GetVbarfoo() string { return t.Vbarfoo } func (t *TestNestedInterfaceImpl) GetVfoobar() string { return t.NestedSquash.Vfoobar } type SquashOnInterfaceType struct { TestInterface `mapstructure:",squash"` Vbar string } type NestedSquash struct { SquashOnInterfaceType `mapstructure:",squash"` Vfoobar string } type SquashOnNestedInterfaceType struct { NestedSquash NestedSquash `mapstructure:",squash"` Vbarfoo 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 NilInterface struct { W io.Writer } type NilPointer struct { Value *string } type Slice struct { Vfoo string Vbar []string } type SliceOfByte struct { Vfoo string Vbar []byte } 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 Remainder struct { A string Extra map[string]interface{} `mapstructure:",remain"` } type StructWithOmitEmpty struct { VisibleStringField string `mapstructure:"visible-string"` OmitStringField string `mapstructure:"omittable-string,omitempty"` VisibleIntField int `mapstructure:"visible-int"` OmitIntField int `mapstructure:"omittable-int,omitempty"` VisibleFloatField float64 `mapstructure:"visible-float"` OmitFloatField float64 `mapstructure:"omittable-float,omitempty"` VisibleSliceField []interface{} `mapstructure:"visible-slice"` OmitSliceField []interface{} `mapstructure:"omittable-slice,omitempty"` VisibleMapField map[string]interface{} `mapstructure:"visible-map"` OmitMapField map[string]interface{} `mapstructure:"omittable-map,omitempty"` NestedField *Nested `mapstructure:"visible-nested"` OmitNestedField *Nested `mapstructure:"omittable-nested,omitempty"` } 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, "vint8": 42, "vint16": 42, "vint32": 42, "vint64": 42, "Vuint": 42, "vbool": true, "Vfloat": 42.42, "vsilent": true, "vdata": 42, "vjsonInt": json.Number("1234"), "vjsonUint": json.Number("1234"), "vjsonUint64": json.Number("9223372036854775809"), // 2^63 + 1 "vjsonFloat": json.Number("1234.5"), "vjsonNumber": json.Number("1234.5"), "vcomplex64": complex(float32(42), float32(42)), "vcomplex128": complex(42, 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.Vint8 != 42 { t.Errorf("vint8 value should be 42: %#v", result.Vint) } if result.Vint16 != 42 { t.Errorf("vint16 value should be 42: %#v", result.Vint) } if result.Vint32 != 42 { t.Errorf("vint32 value should be 42: %#v", result.Vint) } if result.Vint64 != 42 { t.Errorf("vint64 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.VjsonUint != 1234 { t.Errorf("vjsonuint value should be 1234: %#v", result.VjsonUint) } if result.VjsonUint64 != 9223372036854775809 { t.Errorf("vjsonuint64 value should be 9223372036854775809: %#v", result.VjsonUint64) } 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) } if real(result.Vcomplex64) != 42 || imag(result.Vcomplex64) != 42 { t.Errorf("vcomplex64 value shou be 42+42i: %#v", result.Vcomplex64) } if real(result.Vcomplex128) != 42 || imag(result.Vcomplex128) != 42 { t.Errorf("vcomplex64 value shou be 42+42i: %#v", result.Vcomplex128) } } 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 TestBasic_interfaceStruct(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", } var iface interface{} = &Basic{} err := Decode(input, &iface) if err != nil { t.Fatalf("got an err: %s", err) } expected := &Basic{ Vstring: "foo", } if !reflect.DeepEqual(iface, expected) { t.Fatalf("bad: %#v", iface) } } // Issue 187 func TestBasic_interfaceStructNonPtr(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", } var iface interface{} = Basic{} err := Decode(input, &iface) if err != nil { t.Fatalf("got an err: %s", err) } expected := Basic{ Vstring: "foo", } if !reflect.DeepEqual(iface, expected) { t.Fatalf("bad: %#v", iface) } } 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_BasicJSONInline(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", } var result BasicJSONInline d, err := NewDecoder(&DecoderConfig{TagName: "json", SquashTagOption: "inline", Result: &result}) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if err := d.Decode(input); 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_BasicJSONInline(t *testing.T) { t.Parallel() var v interface{} var ok bool input := BasicJSONInline{ Test: Basic{ Vstring: "foo", }, } var result map[string]interface{} d, err := NewDecoder(&DecoderConfig{TagName: "json", SquashTagOption: "inline", Result: &result}) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if err := d.Decode(input); 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_decodeSliceWithArray(t *testing.T) { t.Parallel() var result []int input := [1]int{1} expected := []int{1} if err := Decode(input, &result); err != nil { t.Fatalf("got an err: %s", err.Error()) } if !reflect.DeepEqual(expected, result) { t.Errorf("wanted %+v, got %+v", expected, result) } } func TestDecode_EmbeddedNoSquash(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", "vunique": "bar", } var result Embedded err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vstring != "" { t.Errorf("vstring value should be empty: %#v", result.Vstring) } if result.Vunique != "bar" { t.Errorf("vunique value should be 'bar': %#v", result.Vunique) } } func TestDecode_EmbeddedPointerNoSquash(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", "vunique": "bar", } result := EmbeddedPointer{ Basic: &Basic{}, } err := Decode(input, &result) if err != nil { t.Fatalf("err: %s", err) } if result.Vstring != "" { t.Errorf("vstring value should be empty: %#v", result.Vstring) } 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_EmbeddedPointerSquash_FromStructToMap(t *testing.T) { t.Parallel() input := EmbeddedPointerSquash{ 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 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_EmbeddedPointerSquash_FromMapToStruct(t *testing.T) { t.Parallel() input := map[string]interface{}{ "Vstring": "foo", "Vunique": "bar", } result := EmbeddedPointerSquash{ Basic: &Basic{}, } 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_EmbeddedPointerSquashWithNestedMapstructure_FromStructToMap(t *testing.T) { t.Parallel() vTime := time.Now() input := EmbeddedPointerSquashWithNestedMapstructure{ NestedPointerWithMapstructure: &NestedPointerWithMapstructure{ Vbar: &BasicMapStructure{ Vunique: "bar", Vtime: &vTime, }, }, Vunique: "foo", } var result map[string]interface{} err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } expected := map[string]interface{}{ "vbar": map[string]interface{}{ "vunique": "bar", "time": &vTime, }, "Vunique": "foo", } if !reflect.DeepEqual(result, expected) { t.Errorf("result should be %#v: got %#v", expected, result) } } func TestDecode_EmbeddedPointerSquashWithNestedMapstructure_FromMapToStruct(t *testing.T) { t.Parallel() vTime := time.Now() input := map[string]interface{}{ "vbar": map[string]interface{}{ "vunique": "bar", "time": &vTime, }, "Vunique": "foo", } result := EmbeddedPointerSquashWithNestedMapstructure{ NestedPointerWithMapstructure: &NestedPointerWithMapstructure{}, } err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } expected := EmbeddedPointerSquashWithNestedMapstructure{ NestedPointerWithMapstructure: &NestedPointerWithMapstructure{ Vbar: &BasicMapStructure{ Vunique: "bar", Vtime: &vTime, }, }, Vunique: "foo", } if !reflect.DeepEqual(result, expected) { t.Errorf("result should be %#v: got %#v", expected, result) } } func TestDecode_EmbeddedSquashConfig(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", "vunique": "bar", "Named": map[string]interface{}{ "vstring": "baz", }, } var result EmbeddedAndNamed config := &DecoderConfig{ Squash: true, 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.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) } if result.Named.Vstring != "baz" { t.Errorf("Named.vstring value should be 'baz': %#v", result.Named.Vstring) } } func TestDecodeFrom_EmbeddedSquashConfig(t *testing.T) { t.Parallel() input := EmbeddedAndNamed{ Basic: Basic{Vstring: "foo"}, Named: Basic{Vstring: "baz"}, Vunique: "bar", } result := map[string]interface{}{} config := &DecoderConfig{ Squash: true, Result: &result, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("got an err: %s", err.Error()) } err = decoder.Decode(input) 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) } v, ok = result["Named"] if !ok { t.Error("Named should be present in map") } else { named := v.(map[string]interface{}) v, ok := named["Vstring"] if !ok { t.Error("Named: vstring should be present in map") } else if !reflect.DeepEqual(v, "baz") { t.Errorf("Named: vstring should be 'baz': %#v", v) } } } func TestDecodeFrom_EmbeddedSquashConfig_WithTags(t *testing.T) { t.Parallel() var v interface{} var ok bool input := EmbeddedSquash{ Basic: Basic{ Vstring: "foo", }, Vunique: "bar", } result := map[string]interface{}{} config := &DecoderConfig{ Squash: true, Result: &result, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("got an err: %s", err.Error()) } err = decoder.Decode(input) 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_SquashOnInterfaceType(t *testing.T) { t.Parallel() input := map[string]interface{}{ "VFoo": "42", "VBar": "43", } result := SquashOnInterfaceType{ TestInterface: &TestInterfaceImpl{}, } err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err) } res := result.GetVfoo() if res != "42" { t.Errorf("unexpected value for VFoo: %s", res) } res = result.Vbar if res != "43" { t.Errorf("unexpected value for Vbar: %s", res) } } func TestDecode_SquashOnOuterNestedInterfaceType(t *testing.T) { t.Parallel() input := map[string]interface{}{ "VFoo": "42", "VBar": "43", "Vfoobar": "44", "Vbarfoo": "45", } result := SquashOnNestedInterfaceType{ NestedSquash: NestedSquash{ SquashOnInterfaceType: SquashOnInterfaceType{ TestInterface: &TestInterfaceImpl{}, }, }, } err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err) } res := result.NestedSquash.GetVfoo() if res != "42" { t.Errorf("unexpected value for VFoo: %s", res) } res = result.NestedSquash.Vbar if res != "43" { t.Errorf("unexpected value for Vbar: %s", res) } res = result.NestedSquash.Vfoobar if res != "44" { t.Errorf("unexpected value for Vfoobar: %s", res) } res = result.Vbarfoo if res != "45" { t.Errorf("unexpected value for Vbarfoo: %s", res) } } func TestDecode_SquashOnInnerNestedInterfaceType(t *testing.T) { t.Parallel() input := map[string]interface{}{ "VFoo": "42", "VBar": "43", "Vfoobar": "44", "Vbarfoo": "45", } result := SquashOnInterfaceType{ TestInterface: &TestNestedInterfaceImpl{ SquashOnNestedInterfaceType: SquashOnNestedInterfaceType{ NestedSquash: NestedSquash{ SquashOnInterfaceType: SquashOnInterfaceType{ TestInterface: &TestInterfaceImpl{}, }, }, }, }, } err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err) } res := result.GetVfoo() if res != "42" { t.Errorf("unexpected value for VFoo: %s", res) } res = result.Vbar if res != "43" { t.Errorf("unexpected value for Vbar: %s", res) } res = result.GetVfoobar() if res != "44" { t.Errorf("unexpected value for Vfoobar: %s", res) } res = result.GetVbarfoo() if res != "45" { t.Errorf("unexpected value for Vbarfoo: %s", res) } } func TestDecode_SquashOnNilInterfaceType(t *testing.T) { t.Parallel() input := map[string]interface{}{ "VFoo": "42", "VBar": "43", } result := SquashOnInterfaceType{ TestInterface: nil, } err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err) } res := result.Vbar if res != "43" { t.Errorf("unexpected value for Vbar: %s", res) } } 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 TestDecoder_ErrorUnused_NotSetable(t *testing.T) { t.Parallel() // lowercase vsilent is unexported and cannot be set input := map[string]interface{}{ "vsilent": "false", } 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 TestDecoder_ErrorUnset(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "hello", "foo": "bar", } var result Basic config := &DecoderConfig{ ErrorUnset: 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 TestNotEmptyByteSlice(t *testing.T) { t.Parallel() inputByteSlice := map[string]interface{}{ "vfoo": "foo", "vbar": []byte(`{"bar": "bar"}`), } result := SliceOfByte{ Vfoo: "another foo", Vbar: []byte(`{"bar": "bar bar bar bar bar bar bar bar"}`), } err := Decode(inputByteSlice, &result) if err != nil { t.Fatalf("got unexpected error: %s", err) } expected := SliceOfByte{ Vfoo: "foo", Vbar: []byte(`{"bar": "bar"}`), } if !reflect.DeepEqual(result, expected) { t.Errorf("bad: %#v", result) } } 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, Vint8: 2, Vint16: 2, Vint32: 2, Vint64: 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, "Vint8": int8(2), "Vint16": int16(2), "Vint32": int32(2), "Vint64": int64(2), "Vuint": uint(3), "Vbool": true, "Vfloat": 4.56, "Vextra": "vextra", "Vdata": []byte("data"), "VjsonInt": 0, "VjsonUint": uint(0), "VjsonUint64": uint64(0), "VjsonFloat": 0.0, "VjsonNumber": json.Number(""), "Vcomplex64": complex64(0), "Vcomplex128": complex128(0), }, false, }, { "embedded struct input", &Embedded{ Vunique: "vunique", Basic: Basic{ Vstring: "vstring", Vint: 2, Vint8: 2, Vint16: 2, Vint32: 2, Vint64: 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, "Vint8": int8(2), "Vint16": int16(2), "Vint32": int32(2), "Vint64": int64(2), "Vuint": uint(3), "Vbool": true, "Vfloat": 4.56, "Vextra": "vextra", "Vdata": []byte("data"), "VjsonInt": 0, "VjsonUint": uint(0), "VjsonUint64": uint64(0), "VjsonFloat": 0.0, "VjsonNumber": json.Number(""), "Vcomplex64": complex64(0), "Vcomplex128": complex128(0), }, }, 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 empty slice", &map[string]interface{}{ "Vbar": []string{}, }, &Slice{}, &Slice{ Vbar: []string{}, }, false, }, { "struct with slice of struct property", &SliceOfStruct{ Value: []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{ { 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, }, { "remainder", map[string]interface{}{ "A": "hello", "B": "goodbye", "C": "yo", }, &Remainder{}, &Remainder{ A: "hello", Extra: map[string]interface{}{ "B": "goodbye", "C": "yo", }, }, false, }, { "remainder with no extra", map[string]interface{}{ "A": "hello", }, &Remainder{}, &Remainder{ A: "hello", Extra: nil, }, false, }, { "struct with omitempty tag return non-empty values", &struct { VisibleField interface{} `mapstructure:"visible"` OmitField interface{} `mapstructure:"omittable,omitempty"` }{ VisibleField: nil, OmitField: "string", }, &map[string]interface{}{}, &map[string]interface{}{"visible": nil, "omittable": "string"}, false, }, { "struct with omitempty tag ignore empty values", &struct { VisibleField interface{} `mapstructure:"visible"` OmitField interface{} `mapstructure:"omittable,omitempty"` }{ VisibleField: nil, OmitField: nil, }, &map[string]interface{}{}, &map[string]interface{}{"visible": nil}, false, }, { "remainder with decode to map", &Remainder{ A: "Alabasta", Extra: map[string]interface{}{ "B": "Baratie", "C": "Cocoyasi", }, }, &map[string]interface{}{}, &map[string]interface{}{ "A": "Alabasta", "B": "Baratie", "C": "Cocoyasi", }, false, }, { "remainder with decode to map with non-map field", &struct { A string Extra *struct{} `mapstructure:",remain"` }{ A: "Alabasta", Extra: nil, }, &map[string]interface{}{}, &map[string]interface{}{ "A": "Alabasta", }, 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") } var derr interface { Unwrap() []error } if !errors.As(err, &derr) { t.Fatalf("error should be a type implementing Unwrap() []error, instead: %#v", err) } errs := derr.Unwrap() if errs[0].Error() != "'Vstring' expected type 'string', got unconvertible type 'int', value: '42'" { 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") } if !errors.As(err, &derr) { t.Fatalf("error should be a type implementing Unwrap() []error, instead: %#v", err) } errs = derr.Unwrap() if errs[0].Error() != "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") } if !errors.As(err, &derr) { t.Fatalf("error should be a type implementing Unwrap() []error, instead: %#v", err) } errs = derr.Unwrap() if errs[0].Error() != "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, "vsilent": "false", "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", "Vbar.vsilent", "bar"} sort.Strings(md.Unused) if !reflect.DeepEqual(md.Unused, expectedUnused) { t.Fatalf("bad unused: %#v", md.Unused) } } func TestMetadata(t *testing.T) { t.Parallel() type testResult struct { Vfoo string Vbar BasicPointer } input := map[string]interface{}{ "vfoo": "foo", "vbar": map[string]interface{}{ "vstring": "foo", "Vuint": 42, "vsilent": "false", "foo": "bar", }, "bar": "nil", } var md Metadata var result testResult 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", "Vbar.vsilent", "bar"} sort.Strings(md.Unused) if !reflect.DeepEqual(md.Unused, expectedUnused) { t.Fatalf("bad unused: %#v", md.Unused) } expectedUnset := []string{ "Vbar.Vbool", "Vbar.Vdata", "Vbar.Vextra", "Vbar.Vfloat", "Vbar.Vint", "Vbar.VjsonFloat", "Vbar.VjsonInt", "Vbar.VjsonNumber", } sort.Strings(md.Unset) if !reflect.DeepEqual(md.Unset, expectedUnset) { t.Fatalf("bad unset: %#v", md.Unset) } } 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", "unexported": "value", } var md Metadata var result struct { Foo int Bar string unexported 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{"unexported", "unused"} sort.Strings(md.Unused) if !reflect.DeepEqual(md.Unused, expectedUnused) { t.Fatalf("bad unused: %#v", md.Unused) } } func TestDecode_StructTaggedWithOmitempty_OmitEmptyValues(t *testing.T) { t.Parallel() input := &StructWithOmitEmpty{} var emptySlice []interface{} var emptyMap map[string]interface{} var emptyNested *Nested expected := &map[string]interface{}{ "visible-string": "", "visible-int": 0, "visible-float": 0.0, "visible-slice": emptySlice, "visible-map": emptyMap, "visible-nested": emptyNested, } actual := &map[string]interface{}{} Decode(input, actual) if !reflect.DeepEqual(actual, expected) { t.Fatalf("Decode() expected: %#v, got: %#v", expected, actual) } } func TestDecode_StructTaggedWithOmitempty_KeepNonEmptyValues(t *testing.T) { t.Parallel() input := &StructWithOmitEmpty{ VisibleStringField: "", OmitStringField: "string", VisibleIntField: 0, OmitIntField: 1, VisibleFloatField: 0.0, OmitFloatField: 1.0, VisibleSliceField: nil, OmitSliceField: []interface{}{1}, VisibleMapField: nil, OmitMapField: map[string]interface{}{"k": "v"}, NestedField: nil, OmitNestedField: &Nested{}, } var emptySlice []interface{} var emptyMap map[string]interface{} var emptyNested *Nested expected := &map[string]interface{}{ "visible-string": "", "omittable-string": "string", "visible-int": 0, "omittable-int": 1, "visible-float": 0.0, "omittable-float": 1.0, "visible-slice": emptySlice, "omittable-slice": []interface{}{1}, "visible-map": emptyMap, "omittable-map": map[string]interface{}{"k": "v"}, "visible-nested": emptyNested, "omittable-nested": &Nested{}, } actual := &map[string]interface{}{} Decode(input, actual) if !reflect.DeepEqual(actual, expected) { t.Fatalf("Decode() expected: %#v, got: %#v", expected, actual) } } func TestDecode_mapToStruct(t *testing.T) { type Target struct { String string StringPtr *string } expected := Target{ String: "hello", } var target Target err := Decode(map[string]interface{}{ "string": "hello", "StringPtr": "goodbye", }, &target) if err != nil { t.Fatalf("got error: %s", err) } // Pointers fail reflect test so do those manually if target.StringPtr == nil || *target.StringPtr != "goodbye" { t.Fatalf("bad: %#v", target) } target.StringPtr = nil if !reflect.DeepEqual(target, expected) { t.Fatalf("bad: %#v", target) } } func TestDecoder_MatchName(t *testing.T) { t.Parallel() type Target struct { FirstMatch string `mapstructure:"first_match"` SecondMatch string NoMatch string `mapstructure:"no_match"` } input := map[string]interface{}{ "first_match": "foo", "SecondMatch": "bar", "NO_MATCH": "baz", } expected := Target{ FirstMatch: "foo", SecondMatch: "bar", } var actual Target config := &DecoderConfig{ Result: &actual, MatchName: func(mapKey, fieldName string) bool { return mapKey == fieldName }, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err != nil { t.Fatalf("err: %s", err) } if !reflect.DeepEqual(expected, actual) { t.Fatalf("Decode() expected: %#v, got: %#v", expected, actual) } } func TestDecoder_IgnoreUntaggedFields(t *testing.T) { type Input struct { UntaggedNumber int TaggedNumber int `mapstructure:"tagged_number"` UntaggedString string TaggedString string `mapstructure:"tagged_string"` } input := &Input{ UntaggedNumber: 31, TaggedNumber: 42, UntaggedString: "hidden", TaggedString: "visible", } actual := make(map[string]interface{}) config := &DecoderConfig{ Result: &actual, IgnoreUntaggedFields: true, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err != nil { t.Fatalf("err: %s", err) } expected := map[string]interface{}{ "tagged_number": 42, "tagged_string": "visible", } if !reflect.DeepEqual(expected, actual) { t.Fatalf("Decode() expected: %#v\ngot: %#v", expected, actual) } } func TestDecoder_IgnoreUntaggedFieldsWithStruct(t *testing.T) { type Output struct { UntaggedInt int TaggedNumber int `mapstructure:"tagged_number"` UntaggedString string TaggedString string `mapstructure:"tagged_string"` } input := map[interface{}]interface{}{ "untaggedint": 31, "tagged_number": 41, "untagged_string": "hidden", "tagged_string": "visible", } actual := Output{} config := &DecoderConfig{ Result: &actual, IgnoreUntaggedFields: true, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err != nil { t.Fatalf("err: %s", err) } expected := Output{ TaggedNumber: 41, TaggedString: "visible", } if !reflect.DeepEqual(expected, actual) { t.Fatalf("Decode() expected: %#v\ngot: %#v", expected, actual) } } func TestDecoder_DecodeNilOption(t *testing.T) { t.Parallel() type Transformed struct { Message string When string } helloHook := func(reflect.Type, reflect.Type, interface{}) (interface{}, error) { return Transformed{Message: "hello"}, nil } goodbyeHook := func(reflect.Type, reflect.Type, interface{}) (interface{}, error) { return Transformed{Message: "goodbye"}, nil } appendHook := func(from reflect.Value, to reflect.Value) (interface{}, error) { if from.Kind() == reflect.Map { stringMap := from.Interface().(map[string]interface{}) if stringMap == nil { stringMap = make(map[string]interface{}) } stringMap["when"] = "see you later" return stringMap, nil } return from.Interface(), nil } tests := []struct { name string decodeNil bool input interface{} result Transformed expectedResult Transformed decodeHook DecodeHookFunc }{ { name: "decodeNil=true for nil input with hook", decodeNil: true, input: nil, decodeHook: helloHook, expectedResult: Transformed{Message: "hello"}, }, { name: "decodeNil=true for nil input without hook", decodeNil: true, input: nil, expectedResult: Transformed{Message: ""}, }, { name: "decodeNil=false for nil input with hook", decodeNil: false, input: nil, decodeHook: helloHook, expectedResult: Transformed{Message: ""}, }, { name: "decodeNil=false for nil input without hook", decodeNil: false, input: nil, expectedResult: Transformed{Message: ""}, }, { name: "decodeNil=true for non-nil input without hook", decodeNil: true, input: map[string]interface{}{"message": "bar"}, expectedResult: Transformed{Message: "bar"}, }, { name: "decodeNil=true for non-nil input with hook", decodeNil: true, input: map[string]interface{}{"message": "bar"}, decodeHook: goodbyeHook, expectedResult: Transformed{Message: "goodbye"}, }, { name: "decodeNil=false for non-nil input without hook", decodeNil: false, input: map[string]interface{}{"message": "bar"}, expectedResult: Transformed{Message: "bar"}, }, { name: "decodeNil=false for non-nil input with hook", decodeNil: false, input: map[string]interface{}{"message": "bar"}, decodeHook: goodbyeHook, expectedResult: Transformed{Message: "goodbye"}, }, { name: "decodeNil=true for nil input without hook and non-empty result", decodeNil: true, input: nil, result: Transformed{Message: "foo"}, expectedResult: Transformed{Message: "foo"}, }, { name: "decodeNil=true for nil input with hook and non-empty result", decodeNil: true, input: nil, result: Transformed{Message: "foo"}, decodeHook: helloHook, expectedResult: Transformed{Message: "hello"}, }, { name: "decodeNil=false for nil input without hook and non-empty result", decodeNil: false, input: nil, result: Transformed{Message: "foo"}, expectedResult: Transformed{Message: "foo"}, }, { name: "decodeNil=false for nil input with hook and non-empty result", decodeNil: false, input: nil, result: Transformed{Message: "foo"}, decodeHook: helloHook, expectedResult: Transformed{Message: "foo"}, }, { name: "decodeNil=false for non-nil input with hook that appends a value", decodeNil: false, input: map[string]interface{}{"message": "bar"}, decodeHook: appendHook, expectedResult: Transformed{Message: "bar", When: "see you later"}, }, { name: "decodeNil=true for non-nil input with hook that appends a value", decodeNil: true, input: map[string]interface{}{"message": "bar"}, decodeHook: appendHook, expectedResult: Transformed{Message: "bar", When: "see you later"}, }, { name: "decodeNil=true for nil input with hook that appends a value", decodeNil: true, decodeHook: appendHook, expectedResult: Transformed{When: "see you later"}, }, { name: "decodeNil=false for nil input with hook that appends a value", decodeNil: false, decodeHook: appendHook, expectedResult: Transformed{}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { config := &DecoderConfig{ Result: &test.result, DecodeNil: test.decodeNil, DecodeHook: test.decodeHook, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } if err := decoder.Decode(test.input); err != nil { t.Fatalf("got an err: %s", err) } if test.result != test.expectedResult { t.Errorf("result should be: %#v, got %#v", test.expectedResult, test.result) } }) } } func TestDecoder_ExpandNilStructPointersHookFunc(t *testing.T) { // a decoder hook that expands nil pointers in a struct to their zero value // if the input map contains the corresponding key. decodeHook := func(from reflect.Value, to reflect.Value) (any, error) { if from.Kind() == reflect.Map && to.Kind() == reflect.Map { toElem := to.Type().Elem() if toElem.Kind() == reflect.Ptr && toElem.Elem().Kind() == reflect.Struct { fromRange := from.MapRange() for fromRange.Next() { fromKey := fromRange.Key() fromValue := fromRange.Value() if fromValue.IsNil() { newFromValue := reflect.New(toElem.Elem()) from.SetMapIndex(fromKey, newFromValue) } } } } return from.Interface(), nil } type Struct struct { Name string } type TestConfig struct { Boolean *bool `mapstructure:"boolean"` Struct *Struct `mapstructure:"struct"` MapStruct map[string]*Struct `mapstructure:"map_struct"` } stringMap := map[string]any{ "boolean": nil, "struct": nil, "map_struct": map[string]any{ "struct": nil, }, } var result TestConfig decoder, err := NewDecoder(&DecoderConfig{ Result: &result, DecodeNil: true, DecodeHook: decodeHook, }) if err != nil { t.Fatalf("err: %s", err) } if err := decoder.Decode(stringMap); err != nil { t.Fatalf("got an err: %s", err) } if result.Boolean != nil { t.Errorf("nil Boolean expected, got '%#v'", result.Boolean) } if result.Struct != nil { t.Errorf("nil Struct expected, got '%#v'", result.Struct) } if len(result.MapStruct) == 0 { t.Fatalf("not-empty MapStruct expected, got '%#v'", result.MapStruct) } if _, ok := result.MapStruct["struct"]; !ok { t.Errorf("MapStruct['struct'] expected") } } 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 } mapstructure-2.2.1/reflect_go1_19.go000066400000000000000000000014371467411650400173210ustar00rootroot00000000000000//go:build !go1.20 package mapstructure import "reflect" func isComparable(v reflect.Value) bool { k := v.Kind() switch k { case reflect.Invalid: return false case reflect.Array: switch v.Type().Elem().Kind() { case reflect.Interface, reflect.Array, reflect.Struct: for i := 0; i < v.Type().Len(); i++ { // if !v.Index(i).Comparable() { if !isComparable(v.Index(i)) { return false } } return true } return v.Type().Comparable() case reflect.Interface: // return v.Elem().Comparable() return isComparable(v.Elem()) case reflect.Struct: for i := 0; i < v.NumField(); i++ { return false // if !v.Field(i).Comparable() { if !isComparable(v.Field(i)) { return false } } return true default: return v.Type().Comparable() } } mapstructure-2.2.1/reflect_go1_20.go000066400000000000000000000002601467411650400173020ustar00rootroot00000000000000//go:build go1.20 package mapstructure import "reflect" // TODO: remove once we drop support for Go <1.20 func isComparable(v reflect.Value) bool { return v.Comparable() }