Full Code of go-viper/mapstructure for AI

main 52aa5c6dc1d2 cached
27 files
295.7 KB
91.7k tokens
408 symbols
1 requests
Download .txt
Showing preview only (307K chars total). Download the full file or copy to clipboard to get everything.
Repository: go-viper/mapstructure
Branch: main
Commit: 52aa5c6dc1d2
Files: 27
Total size: 295.7 KB

Directory structure:
gitextract_89bd1yif/

├── .editorconfig
├── .envrc
├── .github/
│   ├── .editorconfig
│   ├── dependabot.yaml
│   └── workflows/
│       ├── analysis-scorecard.yaml
│       └── ci.yaml
├── .gitignore
├── .golangci.yaml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── decode_hooks.go
├── decode_hooks_test.go
├── devenv.nix
├── devenv.yaml
├── errors.go
├── go.mod
├── internal/
│   └── errors/
│       ├── errors.go
│       ├── join.go
│       └── join_go1_19.go
├── mapstructure.go
├── mapstructure_benchmark_test.go
├── mapstructure_bugs_test.go
├── mapstructure_examples_test.go
├── mapstructure_test.go
├── reflect_go1_19.go
└── reflect_go1_20.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = 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

[.golangci.yaml]
indent_size = 2

[devenv.yaml]
indent_size = 2


================================================
FILE: .envrc
================================================
#!/usr/bin/env bash

export DIRENV_WARN_TIMEOUT=20s

eval "$(devenv direnvrc)"

use devenv


================================================
FILE: .github/.editorconfig
================================================
[{*.yml,*.yaml}]
indent_size = 2


================================================
FILE: .github/dependabot.yaml
================================================
version: 2

updates:
  - package-ecosystem: gomod
    directory: /
    schedule:
      interval: daily

  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: daily


================================================
FILE: .github/workflows/analysis-scorecard.yaml
================================================
name: OpenSSF Scorecard

on:
  branch_protection_rule:
  push:
    branches: [main]
  schedule:
    - cron: "30 0 * * 5"

permissions:
  contents: read

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest

    permissions:
      actions: read
      contents: read
      id-token: write
      security-events: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Run analysis
        uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
        with:
          results_file: results.sarif
          results_format: sarif
          publish_results: true

      - name: Upload results as artifact
        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
        with:
          name: OpenSSF Scorecard results
          path: results.sarif
          retention-days: 5

      - name: Upload results to GitHub Security tab
        uses: github/codeql-action/upload-sarif@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v3.29.5
        with:
          sarif_file: results.sarif


================================================
FILE: .github/workflows/ci.yaml
================================================
name: CI

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest

    strategy:
      matrix:
        go:
          [
            "1.18",
            "1.19",
            "1.20",
            "1.21",
            "1.22",
            "1.23",
            "1.24",
            "1.25",
            "stable",
            "oldstable",
          ]

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Set up Go
        uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
        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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Set up Go
        uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
        with:
          go-version: "1.25"

      - name: Lint
        uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
        with:
          version: v2.6.2


================================================
FILE: .gitignore
================================================
/bin/
/build/
/var/

# Devenv
.devenv*
devenv.local.nix
devenv.local.yaml
.direnv
.pre-commit-config.yaml


================================================
FILE: .golangci.yaml
================================================
version: "2"

run:
  timeout: 10m

linters:
  enable:
    - govet
    - ineffassign
    # - misspell
    - nolintlint
    # - revive

  disable:
    - errcheck
    - staticcheck
    - unused

  settings:
    misspell:
      locale: US
    nolintlint:
      allow-unused: false # report any unused nolint directives
      require-specific: false # don't require nolint directives to be specific about which linter is being skipped

formatters:
  enable:
    - gci
    - gofmt
    - gofumpt
    - goimports
    # - golines

  settings:
    gci:
      sections:
        - standard
        - default
        - localmodule
    gofmt:
      simplify: true
      rewrite-rules:
        - pattern: interface{}
          replacement: any

  exclusions:
    paths:
      - internal/


================================================
FILE: CHANGELOG.md
================================================
> [!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.


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2013 Mitchell Hashimoto

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
# mapstructure

[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/go-viper/mapstructure/ci.yaml?style=flat-square)](https://github.com/go-viper/mapstructure/actions/workflows/ci.yaml)
[![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)
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/go-viper/mapstructure?style=flat-square&color=61CFDD)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/go-viper/mapstructure/badge?style=flat-square)](https://deps.dev/go/github.com%252Fgo-viper%252Fmapstructure%252Fv2)

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).


================================================
FILE: decode_hooks.go
================================================
package mapstructure

import (
	"encoding"
	"errors"
	"fmt"
	"net"
	"net/netip"
	"net/url"
	"reflect"
	"strconv"
	"strings"
	"time"
)

// safeInterface safely extracts the interface value from a reflect.Value.
// It returns nil if the value is not valid or is a nil interface.
func safeInterface(v reflect.Value) any {
	if !v.IsValid() {
		return nil
	}
	return v.Interface()
}

// typedDecodeHook takes a raw DecodeHookFunc (an any) 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 := []any{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 any) 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) (any, error) {
	switch f := typedDecodeHook(raw).(type) {
	case DecodeHookFuncType:
		return func(from reflect.Value, to reflect.Value) (any, error) {
			if !from.IsValid() {
				return f(reflect.TypeOf((*any)(nil)).Elem(), to.Type(), nil)
			}
			return f(from.Type(), to.Type(), from.Interface())
		}
	case DecodeHookFuncKind:
		return func(from reflect.Value, to reflect.Value) (any, error) {
			if !from.IsValid() {
				return f(reflect.Invalid, to.Kind(), nil)
			}
			return f(from.Kind(), to.Kind(), from.Interface())
		}
	case DecodeHookFuncValue:
		return func(from reflect.Value, to reflect.Value) (any, error) {
			return f(from, to)
		}
	default:
		return func(from reflect.Value, to reflect.Value) (any, 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,
) (any, error) {
	switch f := typedDecodeHook(raw).(type) {
	case DecodeHookFuncType:
		if !from.IsValid() {
			return f(reflect.TypeOf((*any)(nil)).Elem(), to.Type(), nil)
		}
		return f(from.Type(), to.Type(), from.Interface())
	case DecodeHookFuncKind:
		if !from.IsValid() {
			return f(reflect.Invalid, to.Kind(), nil)
		}
		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) (any, error), 0, len(fs))
	for _, f := range fs {
		cached = append(cached, cachedDecodeHook(f))
	}
	return func(f reflect.Value, t reflect.Value) (any, error) {
		var err error
		data := safeInterface(f)

		newFrom := f
		for _, c := range cached {
			data, err = c(newFrom, t)
			if err != nil {
				return nil, err
			}
			if v, ok := data.(reflect.Value); ok {
				newFrom = v
			} else if data != nil {
				newFrom = reflect.ValueOf(data)
			} else {
				// Keep newFrom as invalid (zero) Value when data is nil
				newFrom = reflect.Value{}
			}
		}

		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) (any, error), 0, len(ff))
	for _, f := range ff {
		cached = append(cached, cachedDecodeHook(f))
	}
	return func(a, b reflect.Value) (any, error) {
		var allErrs string
		var out any
		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 any,
	) (any, 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
	}
}

// StringToWeakSliceHookFunc brings back the old (pre-v2) behavior of [StringToSliceHookFunc].
//
// As of mapstructure v2.0.0 [StringToSliceHookFunc] checks if the return type is a string slice.
// This function removes that check.
func StringToWeakSliceHookFunc(sep string) DecodeHookFunc {
	return func(
		f reflect.Type,
		t reflect.Type,
		data any,
	) (any, error) {
		if f.Kind() != reflect.String || t.Kind() != reflect.Slice {
			return data, nil
		}

		raw := data.(string)
		if raw == "" {
			return []string{}, nil
		}

		return strings.Split(raw, sep), nil
	}
}

// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
// strings to time.Duration.
func StringToTimeDurationHookFunc() DecodeHookFunc {
	return func(
		f reflect.Type,
		t reflect.Type,
		data any,
	) (any, error) {
		if f.Kind() != reflect.String {
			return data, nil
		}
		if t != reflect.TypeOf(time.Duration(5)) {
			return data, nil
		}

		// Convert it by parsing
		d, err := time.ParseDuration(data.(string))

		return d, wrapTimeParseDurationError(err)
	}
}

// StringToTimeLocationHookFunc returns a DecodeHookFunc that converts
// strings to *time.Location.
func StringToTimeLocationHookFunc() DecodeHookFunc {
	return func(
		f reflect.Type,
		t reflect.Type,
		data any,
	) (any, error) {
		if f.Kind() != reflect.String {
			return data, nil
		}
		if t != reflect.TypeOf(time.Local) {
			return data, nil
		}
		d, err := time.LoadLocation(data.(string))

		return d, wrapTimeParseLocationError(err)
	}
}

// StringToURLHookFunc returns a DecodeHookFunc that converts
// strings to *url.URL.
func StringToURLHookFunc() DecodeHookFunc {
	return func(
		f reflect.Type,
		t reflect.Type,
		data any,
	) (any, error) {
		if f.Kind() != reflect.String {
			return data, nil
		}
		if t != reflect.TypeOf(&url.URL{}) {
			return data, nil
		}

		// Convert it by parsing
		u, err := url.Parse(data.(string))

		return u, wrapUrlError(err)
	}
}

// StringToIPHookFunc returns a DecodeHookFunc that converts
// strings to net.IP
func StringToIPHookFunc() DecodeHookFunc {
	return func(
		f reflect.Type,
		t reflect.Type,
		data any,
	) (any, 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")
		}

		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 any,
	) (any, 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, wrapNetParseError(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 any,
	) (any, error) {
		if f.Kind() != reflect.String {
			return data, nil
		}
		if t != reflect.TypeOf(time.Time{}) {
			return data, nil
		}

		// Convert it by parsing
		ti, err := time.Parse(layout, data.(string))

		return ti, wrapTimeParseError(err)
	}
}

// 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 any,
) (any, 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) (any, error) {
		if f.Kind() != reflect.Struct {
			return f.Interface(), nil
		}

		var i any = struct{}{}
		if t.Type() != reflect.TypeOf(&i).Elem() {
			return f.Interface(), nil
		}

		m := make(map[string]any)
		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 any,
	) (any, 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 any,
	) (any, error) {
		if f.Kind() != reflect.String {
			return data, nil
		}
		if t != reflect.TypeOf(netip.Addr{}) {
			return data, nil
		}

		// Convert it by parsing
		addr, err := netip.ParseAddr(data.(string))

		return addr, wrapNetIPParseAddrError(err)
	}
}

// StringToNetIPAddrPortHookFunc returns a DecodeHookFunc that converts
// strings to netip.AddrPort.
func StringToNetIPAddrPortHookFunc() DecodeHookFunc {
	return func(
		f reflect.Type,
		t reflect.Type,
		data any,
	) (any, error) {
		if f.Kind() != reflect.String {
			return data, nil
		}
		if t != reflect.TypeOf(netip.AddrPort{}) {
			return data, nil
		}

		// Convert it by parsing
		addrPort, err := netip.ParseAddrPort(data.(string))

		return addrPort, wrapNetIPParseAddrPortError(err)
	}
}

// StringToNetIPPrefixHookFunc returns a DecodeHookFunc that converts
// strings to netip.Prefix.
func StringToNetIPPrefixHookFunc() DecodeHookFunc {
	return func(
		f reflect.Type,
		t reflect.Type,
		data any,
	) (any, error) {
		if f.Kind() != reflect.String {
			return data, nil
		}
		if t != reflect.TypeOf(netip.Prefix{}) {
			return data, nil
		}

		// Convert it by parsing
		prefix, err := netip.ParsePrefix(data.(string))

		return prefix, wrapNetIPParsePrefixError(err)
	}
}

// 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 any) (any, 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), wrapStrconvNumError(err)
	}
}

// StringToUint8HookFunc returns a DecodeHookFunc that converts
// strings to uint8.
func StringToUint8HookFunc() DecodeHookFunc {
	return func(f reflect.Type, t reflect.Type, data any) (any, 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), wrapStrconvNumError(err)
	}
}

// StringToInt16HookFunc returns a DecodeHookFunc that converts
// strings to int16.
func StringToInt16HookFunc() DecodeHookFunc {
	return func(f reflect.Type, t reflect.Type, data any) (any, 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), wrapStrconvNumError(err)
	}
}

// StringToUint16HookFunc returns a DecodeHookFunc that converts
// strings to uint16.
func StringToUint16HookFunc() DecodeHookFunc {
	return func(f reflect.Type, t reflect.Type, data any) (any, 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), wrapStrconvNumError(err)
	}
}

// StringToInt32HookFunc returns a DecodeHookFunc that converts
// strings to int32.
func StringToInt32HookFunc() DecodeHookFunc {
	return func(f reflect.Type, t reflect.Type, data any) (any, 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), wrapStrconvNumError(err)
	}
}

// StringToUint32HookFunc returns a DecodeHookFunc that converts
// strings to uint32.
func StringToUint32HookFunc() DecodeHookFunc {
	return func(f reflect.Type, t reflect.Type, data any) (any, 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), wrapStrconvNumError(err)
	}
}

// StringToInt64HookFunc returns a DecodeHookFunc that converts
// strings to int64.
func StringToInt64HookFunc() DecodeHookFunc {
	return func(f reflect.Type, t reflect.Type, data any) (any, error) {
		if f.Kind() != reflect.String || t.Kind() != reflect.Int64 {
			return data, nil
		}

		// Convert it by parsing
		i64, err := strconv.ParseInt(data.(string), 0, 64)
		return int64(i64), wrapStrconvNumError(err)
	}
}

// StringToUint64HookFunc returns a DecodeHookFunc that converts
// strings to uint64.
func StringToUint64HookFunc() DecodeHookFunc {
	return func(f reflect.Type, t reflect.Type, data any) (any, error) {
		if f.Kind() != reflect.String || t.Kind() != reflect.Uint64 {
			return data, nil
		}

		// Convert it by parsing
		u64, err := strconv.ParseUint(data.(string), 0, 64)
		return uint64(u64), wrapStrconvNumError(err)
	}
}

// StringToIntHookFunc returns a DecodeHookFunc that converts
// strings to int.
func StringToIntHookFunc() DecodeHookFunc {
	return func(f reflect.Type, t reflect.Type, data any) (any, 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), wrapStrconvNumError(err)
	}
}

// StringToUintHookFunc returns a DecodeHookFunc that converts
// strings to uint.
func StringToUintHookFunc() DecodeHookFunc {
	return func(f reflect.Type, t reflect.Type, data any) (any, 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), wrapStrconvNumError(err)
	}
}

// StringToFloat32HookFunc returns a DecodeHookFunc that converts
// strings to float32.
func StringToFloat32HookFunc() DecodeHookFunc {
	return func(f reflect.Type, t reflect.Type, data any) (any, 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), wrapStrconvNumError(err)
	}
}

// StringToFloat64HookFunc returns a DecodeHookFunc that converts
// strings to float64.
func StringToFloat64HookFunc() DecodeHookFunc {
	return func(f reflect.Type, t reflect.Type, data any) (any, error) {
		if f.Kind() != reflect.String || t.Kind() != reflect.Float64 {
			return data, nil
		}

		// Convert it by parsing
		f64, err := strconv.ParseFloat(data.(string), 64)
		return f64, wrapStrconvNumError(err)
	}
}

// StringToBoolHookFunc returns a DecodeHookFunc that converts
// strings to bool.
func StringToBoolHookFunc() DecodeHookFunc {
	return func(f reflect.Type, t reflect.Type, data any) (any, error) {
		if f.Kind() != reflect.String || t.Kind() != reflect.Bool {
			return data, nil
		}

		// Convert it by parsing
		b, err := strconv.ParseBool(data.(string))
		return b, wrapStrconvNumError(err)
	}
}

// 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 any) (any, 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), wrapStrconvNumError(err)
	}
}

// StringToComplex128HookFunc returns a DecodeHookFunc that converts
// strings to complex128.
func StringToComplex128HookFunc() DecodeHookFunc {
	return func(f reflect.Type, t reflect.Type, data any) (any, error) {
		if f.Kind() != reflect.String || t.Kind() != reflect.Complex128 {
			return data, nil
		}

		// Convert it by parsing
		c128, err := strconv.ParseComplex(data.(string), 128)
		return c128, wrapStrconvNumError(err)
	}
}


================================================
FILE: decode_hooks_test.go
================================================
package mapstructure

import (
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"math/big"
	"net"
	"net/netip"
	"net/url"
	"reflect"
	"strings"
	"testing"
	"time"
)

type decodeHookTestSuite[F any, T any] struct {
	fn   DecodeHookFunc
	ok   []decodeHookTestCase[F, T]
	fail []decodeHookFailureTestCase[F, T]
}

func (ts decodeHookTestSuite[F, T]) Run(t *testing.T) {
	t.Run("OK", func(t *testing.T) {
		t.Parallel()

		for _, tc := range ts.ok {
			tc := tc

			t.Run("", func(t *testing.T) {
				t.Parallel()

				tc.Run(t, ts.fn)
			})
		}
	})

	t.Run("Fail", func(t *testing.T) {
		t.Parallel()

		for _, tc := range ts.fail {
			tc := tc

			t.Run("", func(t *testing.T) {
				t.Parallel()

				tc.Run(t, ts.fn)
			})
		}
	})

	t.Run("NoOp", func(t *testing.T) {
		t.Parallel()

		var zero F

		actual, err := DecodeHookExec(ts.fn, reflect.ValueOf(zero), reflect.ValueOf(zero))
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}

		if !reflect.DeepEqual(actual, zero) {
			t.Fatalf("expected %[1]T(%#[1]v), got %[2]T(%#[2]v)", zero, actual)
		}
	})
}

type decodeHookTestCase[F any, T any] struct {
	from     F
	expected T
}

func (tc decodeHookTestCase[F, T]) Run(t *testing.T, fn DecodeHookFunc) {
	var to T

	actual, err := DecodeHookExec(fn, reflect.ValueOf(tc.from), reflect.ValueOf(to))
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	if !reflect.DeepEqual(actual, tc.expected) {
		t.Fatalf("expected %[1]T(%#[1]v), got %[2]T(%#[2]v)", tc.expected, actual)
	}
}

type decodeHookFailureTestCase[F any, T any] struct {
	from F
}

func (tc decodeHookFailureTestCase[F, T]) Run(t *testing.T, fn DecodeHookFunc) {
	var to T

	_, err := DecodeHookExec(fn, reflect.ValueOf(tc.from), reflect.ValueOf(to))
	if err == nil {
		t.Fatalf("expected error, got none")
	}
}

func TestComposeDecodeHookFunc(t *testing.T) {
	f1 := func(
		f reflect.Kind,
		t reflect.Kind,
		data any,
	) (any, error) {
		return data.(string) + "foo", nil
	}

	f2 := func(
		f reflect.Kind,
		t reflect.Kind,
		data any,
	) (any, 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, any) (any, error) {
		return nil, errors.New("foo")
	}

	f2 := func(reflect.Kind, reflect.Kind, any) (any, 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 any,
	) (any, error) {
		return int(42), nil
	}

	f2 := func(
		f reflect.Kind,
		t reflect.Kind,
		data any,
	) (any, 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 any,
	) (any, error) {
		return data.(string) + "foo", nil
	}

	f2 := func(
		f reflect.Kind,
		t reflect.Kind,
		data any,
	) (any, 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 any,
	) (any, error) {
		return nil, errors.New("f1 error")
	}

	f2 := func(
		f reflect.Kind,
		t reflect.Kind,
		data any,
	) (any, error) {
		return nil, errors.New("f2 error")
	}

	f3 := func(
		f reflect.Kind,
		t reflect.Kind,
		data any,
	) (any, 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 any,
	) (any, error) {
		return nil, errors.New("f1 error")
	}

	f2 := func(
		f reflect.Kind,
		t reflect.Kind,
		data any,
	) (any, 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 TestComposeDecodeHookFunc_ReflectValueHook(t *testing.T) {
	reflectValueHook := func(
		f reflect.Kind,
		t reflect.Kind,
		data any,
	) (any, error) {
		new := data.(string) + "foo"
		return reflect.ValueOf(new), nil
	}

	stringHook := func(
		f reflect.Kind,
		t reflect.Kind,
		data any,
	) (any, error) {
		return data.(string) + "bar", nil
	}

	f := ComposeDecodeHookFunc(reflectValueHook, stringHook)

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

// TestComposeDecodeHookFunc_NilValue tests that ComposeDecodeHookFunc
// doesn't panic when a hook returns nil (issue #121).
func TestComposeDecodeHookFunc_NilValue(t *testing.T) {
	hook := func(f reflect.Kind, t reflect.Kind, data any) (any, error) {
		return data, nil
	}

	f := ComposeDecodeHookFunc(hook, hook)

	// Test with nil input - this should not panic
	result, err := DecodeHookExec(f, reflect.Value{}, reflect.ValueOf(""))
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	if result != nil {
		t.Fatalf("expected nil result, got: %#v", result)
	}
}

// TestComposeDecodeHookFunc_DecodeNilRemain tests the specific scenario from issue #121
// where using ComposeDecodeHookFunc with DecodeNil and ,remain tag causes a panic.
func TestComposeDecodeHookFunc_DecodeNilRemain(t *testing.T) {
	v := make(map[string]any)
	v["m"] = nil

	var result struct {
		V map[string]any `mapstructure:",remain"`
	}

	hook := func(f reflect.Kind, t reflect.Kind, data any) (any, error) {
		return data, nil
	}

	dec, err := NewDecoder(&DecoderConfig{
		DecodeHook: ComposeDecodeHookFunc(hook, hook),
		DecodeNil:  true,
		Result:     &result,
	})
	if err != nil {
		t.Fatalf("unexpected error creating decoder: %s", err)
	}

	// This should not panic
	err = dec.Decode(&v)
	if err != nil {
		t.Fatalf("unexpected error decoding: %s", err)
	}
}

func TestStringToSliceHookFunc(t *testing.T) {
	// Test comma separator
	commaSuite := decodeHookTestSuite[string, []string]{
		fn: StringToSliceHookFunc(","),
		ok: []decodeHookTestCase[string, []string]{
			{"foo,bar,baz", []string{"foo", "bar", "baz"}}, // Basic comma separation
			{"", []string{}},                                                                    // Empty string
			{"single", []string{"single"}},                                                      // Single element
			{"one,two", []string{"one", "two"}},                                                 // Two elements
			{"foo, bar, baz", []string{"foo", " bar", " baz"}},                                  // Preserves spaces
			{"foo,,bar", []string{"foo", "", "bar"}},                                            // Empty elements
			{",foo,bar,", []string{"", "foo", "bar", ""}},                                       // Leading/trailing separators
			{"foo,bar,baz,", []string{"foo", "bar", "baz", ""}},                                 // Trailing separator
			{",foo", []string{"", "foo"}},                                                       // Leading separator only
			{"foo,", []string{"foo", ""}},                                                       // Trailing separator only
			{"a,b,c,d,e,f,g,h,i,j", []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}}, // Many elements
		},
		fail: []decodeHookFailureTestCase[string, []string]{
			// StringToSliceHookFunc doesn't have failure cases - it always succeeds
		},
	}
	t.Run("CommaSeparator", commaSuite.Run)

	// Test semicolon separator
	semicolonSuite := decodeHookTestSuite[string, []string]{
		fn: StringToSliceHookFunc(";"),
		ok: []decodeHookTestCase[string, []string]{
			{"foo;bar;baz", []string{"foo", "bar", "baz"}}, // Basic semicolon separation
			{"", []string{}},                                   // Empty string
			{"single", []string{"single"}},                     // Single element
			{"one;two", []string{"one", "two"}},                // Two elements
			{"foo; bar; baz", []string{"foo", " bar", " baz"}}, // Preserves spaces
			{"foo;;bar", []string{"foo", "", "bar"}},           // Empty elements
			{";foo;bar;", []string{"", "foo", "bar", ""}},      // Leading/trailing separators
		},
		fail: []decodeHookFailureTestCase[string, []string]{},
	}
	t.Run("SemicolonSeparator", semicolonSuite.Run)

	// Test pipe separator
	pipeSuite := decodeHookTestSuite[string, []string]{
		fn: StringToSliceHookFunc("|"),
		ok: []decodeHookTestCase[string, []string]{
			{"foo|bar|baz", []string{"foo", "bar", "baz"}}, // Basic pipe separation
			{"", []string{}},                         // Empty string
			{"single", []string{"single"}},           // Single element
			{"foo||bar", []string{"foo", "", "bar"}}, // Empty elements
		},
		fail: []decodeHookFailureTestCase[string, []string]{},
	}
	t.Run("PipeSeparator", pipeSuite.Run)

	// Test space separator
	spaceSuite := decodeHookTestSuite[string, []string]{
		fn: StringToSliceHookFunc(" "),
		ok: []decodeHookTestCase[string, []string]{
			{"foo bar baz", []string{"foo", "bar", "baz"}}, // Basic space separation
			{"", []string{}},                         // Empty string
			{"single", []string{"single"}},           // Single element
			{"foo  bar", []string{"foo", "", "bar"}}, // Double space creates empty element
		},
		fail: []decodeHookFailureTestCase[string, []string]{},
	}
	t.Run("SpaceSeparator", spaceSuite.Run)

	// Test multi-character separator
	multiCharSuite := decodeHookTestSuite[string, []string]{
		fn: StringToSliceHookFunc("::"),
		ok: []decodeHookTestCase[string, []string]{
			{"foo::bar::baz", []string{"foo", "bar", "baz"}}, // Basic multi-char separation
			{"", []string{}},                                 // Empty string
			{"single", []string{"single"}},                   // Single element
			{"foo::::bar", []string{"foo", "", "bar"}},       // Double separator creates empty element
			{"::foo::bar::", []string{"", "foo", "bar", ""}}, // Leading/trailing separators
		},
		fail: []decodeHookFailureTestCase[string, []string]{},
	}
	t.Run("MultiCharSeparator", multiCharSuite.Run)

	// Test edge cases with custom logic for type conversion
	t.Run("NonStringTypes", func(t *testing.T) {
		f := StringToSliceHookFunc(",")

		// Test that non-string types are passed through unchanged
		sliceValue := reflect.ValueOf([]string{"42"})
		actual, err := DecodeHookExec(f, sliceValue, sliceValue)
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		if !reflect.DeepEqual(actual, []string{"42"}) {
			t.Fatalf("expected %v, got %v", []string{"42"}, actual)
		}

		// Test byte slice passthrough
		byteValue := reflect.ValueOf([]byte("42"))
		actual, err = DecodeHookExec(f, byteValue, reflect.ValueOf([]byte{}))
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		if !reflect.DeepEqual(actual, []byte("42")) {
			t.Fatalf("expected %v, got %v", []byte("42"), actual)
		}

		// Test string to string passthrough
		strValue := reflect.ValueOf("42")
		actual, err = DecodeHookExec(f, strValue, strValue)
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		if !reflect.DeepEqual(actual, "42") {
			t.Fatalf("expected %v, got %v", "42", actual)
		}
	})
}

func TestStringToWeakSliceHookFunc(t *testing.T) {
	f := StringToWeakSliceHookFunc(",")

	strValue := reflect.ValueOf("42")
	sliceValue := reflect.ValueOf([]string{"42"})
	sliceValue2 := reflect.ValueOf([]byte("42"))

	cases := []struct {
		f, t   reflect.Value
		result any
		err    bool
	}{
		{sliceValue, sliceValue, []string{"42"}, false},
		{sliceValue2, sliceValue2, []byte("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("foo,bar,baz"),
			sliceValue2,
			[]string{"foo", "bar", "baz"},
			false,
		},
		{
			reflect.ValueOf(""),
			sliceValue,
			[]string{},
			false,
		},
		{
			reflect.ValueOf(""),
			sliceValue2,
			[]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) {
	suite := decodeHookTestSuite[string, time.Duration]{
		fn: StringToTimeDurationHookFunc(),
		ok: []decodeHookTestCase[string, time.Duration]{
			// Basic units
			{"5s", 5 * time.Second},            // Seconds
			{"10ms", 10 * time.Millisecond},    // Milliseconds
			{"100us", 100 * time.Microsecond},  // Microseconds
			{"1000ns", 1000 * time.Nanosecond}, // Nanoseconds
			{"2m", 2 * time.Minute},            // Minutes
			{"3h", 3 * time.Hour},              // Hours
			{"24h", 24 * time.Hour},            // Day in hours

			// Combinations
			{"1h30m", time.Hour + 30*time.Minute},                       // Hour and minutes
			{"2h45m30s", 2*time.Hour + 45*time.Minute + 30*time.Second}, // Multiple units
			{"1m30s", time.Minute + 30*time.Second},                     // Minutes and seconds
			{"500ms", 500 * time.Millisecond},                           // Milliseconds only
			{"1.5s", time.Second + 500*time.Millisecond},                // Fractional seconds
			{"2.5h", 2*time.Hour + 30*time.Minute},                      // Fractional hours

			// Zero values
			{"0s", 0},  // Zero seconds
			{"0ms", 0}, // Zero milliseconds
			{"0h", 0},  // Zero hours
			{"0", 0},   // Just zero

			// Negative durations
			{"-5s", -5 * time.Second},                 // Negative seconds
			{"-1h30m", -(time.Hour + 30*time.Minute)}, // Negative combined
			{"-100ms", -100 * time.Millisecond},       // Negative milliseconds

			// Fractional values
			{"0.5s", 500 * time.Millisecond},                     // Half second
			{"1.25m", time.Minute + 15*time.Second},              // Fractional minute
			{"0.1h", 6 * time.Minute},                            // Fractional hour
			{"2.5ms", 2*time.Millisecond + 500*time.Microsecond}, // Fractional millisecond

			// Large values
			{"8760h", 8760 * time.Hour},               // 1 year in hours
			{"525600m", 525600 * time.Minute},         // 1 year in minutes
			{"1000000us", 1000000 * time.Microsecond}, // Large microseconds

			// Additional valid cases
			{".5s", 500 * time.Millisecond},            // Leading decimal is valid
			{"5µs", 5 * time.Microsecond},              // Unicode micro symbol is valid
			{"5.s", 5 * time.Second},                   // Trailing decimal is valid
			{"5s5m5s", 10*time.Second + 5*time.Minute}, // Duplicate units are valid
		},
		fail: []decodeHookFailureTestCase[string, time.Duration]{
			{"5"},        // Missing unit
			{"abc"},      // Invalid format
			{""},         // Empty string
			{"5x"},       // Invalid unit
			{"5ss"},      // Double unit letters
			{"5..5s"},    // Multiple decimal points
			{"++5s"},     // Double plus sign
			{"--5s"},     // Double minus sign
			{" 5s "},     // Leading/trailing whitespace not handled
			{"\t10ms\n"}, // Tab/newline whitespace not handled
			{"\r1h\r"},   // Carriage return whitespace not handled
			{"5s "},      // Trailing space after unit
			{" 5 s"},     // Space before unit
			{"5 s 10 m"}, // Spaces in combined duration
			{"∞s"},       // Unicode infinity symbol
			{"1y"},       // Unsupported unit (years)
			{"1w"},       // Unsupported unit (weeks)
			{"1d"},       // Unsupported unit (days)
		},
	}

	// Test non-string and non-duration type passthrough
	t.Run("Passthrough", func(t *testing.T) {
		f := StringToTimeDurationHookFunc()

		// Non-string type should pass through
		intValue := reflect.ValueOf(42)
		actual, err := DecodeHookExec(f, intValue, reflect.ValueOf(time.Duration(0)))
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		if actual != 42 {
			t.Fatalf("expected 42, got %v", actual)
		}

		// Non-duration target type should pass through
		strValue := reflect.ValueOf("5s")
		actual, err = DecodeHookExec(f, strValue, strValue)
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		if actual != "5s" {
			t.Fatalf("expected '5s', got %v", actual)
		}
	})

	suite.Run(t)
}

func TestStringToTimeLocationHookFunc(t *testing.T) {
	newYork, _ := time.LoadLocation("America/New_York")
	london, _ := time.LoadLocation("Europe/London")
	tehran, _ := time.LoadLocation("Asia/Tehran")
	shanghai, _ := time.LoadLocation("Asia/Shanghai")

	suite := decodeHookTestSuite[string, *time.Location]{
		fn: StringToTimeLocationHookFunc(),
		ok: []decodeHookTestCase[string, *time.Location]{
			{"UTC", time.UTC},
			{"Local", time.Local},
			{"America/New_York", newYork},
			{"Europe/London", london},
			{"Asia/Tehran", tehran},
			{"Asia/Shanghai", shanghai},
		},
		fail: []decodeHookFailureTestCase[string, *time.Location]{
			{"UTC2"},           // Non-existent
			{"5s"},             // Duration-like, not a zone
			{"Europe\\London"}, // Invalid path separator
			{"../etc/passwd"},  // Unsafe path
			{"/etc/zoneinfo"},  // Absolute path (rejected by stdlib)
			{"Asia\\Tehran"},   // Invalid Windows-style path
		},
	}

	suite.Run(t)
}

func TestStringToURLHookFunc(t *testing.T) {
	httpURL, _ := url.Parse("http://example.com")
	httpsURL, _ := url.Parse("https://example.com")
	ftpURL, _ := url.Parse("ftp://ftp.example.com")
	fileURL, _ := url.Parse("file:///path/to/file")
	complexURL, _ := url.Parse("https://user:pass@example.com:8080/path?query=value&foo=bar#fragment")
	ipURL, _ := url.Parse("http://192.168.1.1:8080")
	ipv6URL, _ := url.Parse("http://[::1]:8080")
	emptyURL, _ := url.Parse("")

	suite := decodeHookTestSuite[string, *url.URL]{
		fn: StringToURLHookFunc(),
		ok: []decodeHookTestCase[string, *url.URL]{
			{"http://example.com", httpURL},   // Basic HTTP URL
			{"https://example.com", httpsURL}, // HTTPS URL
			{"ftp://ftp.example.com", ftpURL}, // FTP URL
			{"file:///path/to/file", fileURL}, // File URL
			{"https://user:pass@example.com:8080/path?query=value&foo=bar#fragment", complexURL}, // Complex URL with all components
			{"http://192.168.1.1:8080", ipURL},                                                   // IPv4 address with port
			{"http://[::1]:8080", ipv6URL},                                                       // IPv6 address with port
			{"", emptyURL},                                                                       // Empty URL
			// Additional valid cases that url.Parse accepts
			{"http://", func() *url.URL { u, _ := url.Parse("http://"); return u }()},                                   // Scheme with empty host
			{"http://example.com:99999", func() *url.URL { u, _ := url.Parse("http://example.com:99999"); return u }()}, // High port number
			{"not a url at all", func() *url.URL { u, _ := url.Parse("not a url at all"); return u }()},                 // Relative path (valid)
		},
		fail: []decodeHookFailureTestCase[string, *url.URL]{
			{"http ://example.com"},  // Space in scheme
			{"://invalid"},           // Missing scheme
			{"http://[invalid:ipv6"}, // Malformed IPv6 bracket
		},
	}

	suite.Run(t)
}

func TestStringToTimeHookFunc(t *testing.T) {
	strValue := reflect.ValueOf("5")
	timeValue := reflect.ValueOf(time.Time{})
	cases := []struct {
		f, t   reflect.Value
		layout string
		result any
		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) {
	suite := decodeHookTestSuite[string, net.IP]{
		fn: StringToIPHookFunc(),
		ok: []decodeHookTestCase[string, net.IP]{
			// IPv4 addresses
			{"1.2.3.4", net.IPv4(0x01, 0x02, 0x03, 0x04)},     // Basic IPv4
			{"192.168.1.1", net.IPv4(192, 168, 1, 1)},         // Private network address
			{"0.0.0.0", net.IPv4(0, 0, 0, 0)},                 // Zero address
			{"255.255.255.255", net.IPv4(255, 255, 255, 255)}, // Broadcast address
			{"127.0.0.1", net.IPv4(127, 0, 0, 1)},             // Localhost
			{"10.0.0.1", net.IPv4(10, 0, 0, 1)},               // Private network
			// IPv6 addresses
			{"::1", net.ParseIP("::1")},                 // IPv6 localhost
			{"2001:db8::1", net.ParseIP("2001:db8::1")}, // Documentation address
			{"fe80::1", net.ParseIP("fe80::1")},         // Link-local address
			{"2001:0db8:85a3:0000:0000:8a2e:0370:7334", net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")}, // Full IPv6 address
			{"2001:db8:85a3::8a2e:370:7334", net.ParseIP("2001:db8:85a3::8a2e:370:7334")},                       // Compressed IPv6
			{"::", net.ParseIP("::")},                             // IPv6 zero address
			{"::ffff:192.0.2.1", net.ParseIP("::ffff:192.0.2.1")}, // IPv4-mapped IPv6
		},
		fail: []decodeHookFailureTestCase[string, net.IP]{
			{"5"},                 // Single number
			{"256.1.1.1"},         // IPv4 octet too large
			{"1.2.3"},             // Too few IPv4 octets
			{"1.2.3.4.5"},         // Too many IPv4 octets
			{"not.an.ip.address"}, // Non-numeric text
			{""},                  // Empty string
			{"192.168.1.256"},     // Last octet too large
			{"192.168.-1.1"},      // Negative octet
			{"gggg::1"},           // Invalid hex in IPv6
			{"2001:db8::1::2"},    // Double :: in IPv6
			{"[::1]"},             // IPv6 with brackets (not raw IP)
			{"192.168.1.1:8080"},  // IPv4 with port
		},
	}

	suite.Run(t)
}

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 any
		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 any
		err    bool
	}{
		// TO STRING
		{
			reflect.ValueOf(false),
			strValue,
			"0", // bool false converts to "0"
			false,
		},

		{
			reflect.ValueOf(true),
			strValue,
			"1", // bool true converts to "1"
			false,
		},

		{
			reflect.ValueOf(float32(7)),
			strValue,
			"7", // float32 converts to string
			false,
		},

		{
			reflect.ValueOf(int(7)),
			strValue,
			"7", // int converts to string
			false,
		},

		{
			reflect.ValueOf([]uint8("foo")),
			strValue,
			"foo", // byte slice converts to string
			false,
		},

		{
			reflect.ValueOf(uint(7)),
			strValue,
			"7", // uint converts to 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 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]any{
		"Sub": map[string]any{
			"TestKey": "testval",
		},
	}

	cases := []struct {
		name     string
		receiver any
		input    any
		expected any
		err      bool
	}{
		{
			"map receiver",
			func() any {
				var res map[string]any
				return &res
			}(),
			testStruct,
			&testMap,
			false,
		},
		{
			"interface receiver",
			func() any {
				var res any
				return &res
			}(),
			testStruct,
			func() any {
				var exp any = testMap
				return &exp
			}(),
			false,
		},
		{
			"slice receiver errors",
			func() any {
				var res []string
				return &res
			}(),
			testStruct,
			new([]string),
			true,
		},
		{
			"slice to slice - no change",
			func() any {
				var res []string
				return &res
			}(),
			[]string{"a", "b"},
			&[]string{"a", "b"},
			false,
		},
		{
			"string to string - no change",
			func() any {
				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 any
		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) {
	suite := decodeHookTestSuite[string, netip.Addr]{
		fn: StringToNetIPAddrHookFunc(),
		ok: []decodeHookTestCase[string, netip.Addr]{
			// IPv4 addresses
			{"192.0.2.1", netip.AddrFrom4([4]byte{0xc0, 0x00, 0x02, 0x01})},   // Documentation address
			{"127.0.0.1", netip.AddrFrom4([4]byte{127, 0, 0, 1})},             // Localhost
			{"0.0.0.0", netip.AddrFrom4([4]byte{0, 0, 0, 0})},                 // Zero address
			{"255.255.255.255", netip.AddrFrom4([4]byte{255, 255, 255, 255})}, // Broadcast address
			{"10.0.0.1", netip.AddrFrom4([4]byte{10, 0, 0, 1})},               // Private network
			{"192.168.1.100", netip.AddrFrom4([4]byte{192, 168, 1, 100})},     // Private network
			// IPv6 addresses
			{"::1", netip.AddrFrom16([16]byte{15: 1})},                                               // IPv6 localhost
			{"2001:db8::1", netip.AddrFrom16([16]byte{0x20, 0x01, 0x0d, 0xb8, 12: 0, 0, 0, 1})},      // Documentation address
			{"fe80::1", netip.AddrFrom16([16]byte{0xfe, 0x80, 14: 0, 1})},                            // Link-local address
			{"::", netip.AddrFrom16([16]byte{})},                                                     // IPv6 zero address
			{"::ffff:192.0.2.1", netip.AddrFrom16([16]byte{10: 0xff, 0xff, 0xc0, 0x00, 0x02, 0x01})}, // IPv4-mapped IPv6
		},
		fail: []decodeHookFailureTestCase[string, netip.Addr]{
			{"5"},                 // Single number
			{"256.1.1.1"},         // IPv4 octet too large
			{"1.2.3"},             // Too few IPv4 octets
			{"1.2.3.4.5"},         // Too many IPv4 octets
			{"not.an.ip.address"}, // Non-numeric text
			{""},                  // Empty string
			{"192.168.1.256"},     // Last octet too large
			{"192.168.-1.1"},      // Negative octet
			{"gggg::1"},           // Invalid hex in IPv6
			{"2001:db8::1::2"},    // Double :: in IPv6
			{"[::1]"},             // IPv6 with brackets
			{"192.168.1.1:8080"},  // IPv4 with port
			{"192.168.1.1/24"},    // IPv4 with CIDR
		},
	}

	suite.Run(t)
}

func TestStringToNetIPAddrPortHookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, netip.AddrPort]{
		fn: StringToNetIPAddrPortHookFunc(),
		ok: []decodeHookTestCase[string, netip.AddrPort]{
			// IPv4 with ports
			{"192.0.2.1:80", netip.AddrPortFrom(netip.AddrFrom4([4]byte{0xc0, 0x00, 0x02, 0x01}), 80)},         // HTTP port
			{"127.0.0.1:8080", netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), 8080)},               // Alternative HTTP port
			{"10.0.0.1:443", netip.AddrPortFrom(netip.AddrFrom4([4]byte{10, 0, 0, 1}), 443)},                   // HTTPS port
			{"192.168.1.100:22", netip.AddrPortFrom(netip.AddrFrom4([4]byte{192, 168, 1, 100}), 22)},           // SSH port
			{"0.0.0.0:0", netip.AddrPortFrom(netip.AddrFrom4([4]byte{0, 0, 0, 0}), 0)},                         // Zero address and port
			{"255.255.255.255:65535", netip.AddrPortFrom(netip.AddrFrom4([4]byte{255, 255, 255, 255}), 65535)}, // Max address and port
			// IPv6 with ports
			{"[::1]:80", netip.AddrPortFrom(netip.AddrFrom16([16]byte{15: 1}), 80)},                                               // IPv6 localhost with HTTP
			{"[2001:db8::1]:443", netip.AddrPortFrom(netip.AddrFrom16([16]byte{0x20, 0x01, 0x0d, 0xb8, 12: 0, 0, 0, 1}), 443)},    // Documentation address with HTTPS
			{"[fe80::1]:8080", netip.AddrPortFrom(netip.AddrFrom16([16]byte{0xfe, 0x80, 14: 0, 1}), 8080)},                        // Link-local with alt HTTP
			{"[::]:22", netip.AddrPortFrom(netip.AddrFrom16([16]byte{}), 22)},                                                     // IPv6 zero address with SSH
			{"[::ffff:192.0.2.1]:80", netip.AddrPortFrom(netip.AddrFrom16([16]byte{10: 0xff, 0xff, 0xc0, 0x00, 0x02, 0x01}), 80)}, // IPv4-mapped IPv6 with HTTP
		},
		fail: []decodeHookFailureTestCase[string, netip.AddrPort]{
			{"5"},                  // Just a number
			{"192.168.1.1"},        // Missing port
			{"192.168.1.1:"},       // Empty port
			{"192.168.1.1:65536"},  // Port too large
			{"192.168.1.1:-1"},     // Negative port
			{"192.168.1.1:abc"},    // Non-numeric port
			{"256.1.1.1:80"},       // Invalid IP
			{"::1:80"},             // IPv6 without brackets
			{"[::1"},               // Missing closing bracket
			{"::1]:80"},            // Missing opening bracket
			{"[invalid::ip]:80"},   // Invalid IPv6
			{""},                   // Empty string
			{":80"},                // Missing IP
			{"192.168.1.1:80:443"}, // Multiple ports
		},
	}

	suite.Run(t)
}

func TestStringToNetIPPrefixHookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, netip.Prefix]{
		fn: StringToNetIPPrefixHookFunc(),
		ok: []decodeHookTestCase[string, netip.Prefix]{
			// IPv4 CIDR notation
			{"192.0.2.1/24", netip.PrefixFrom(netip.AddrFrom4([4]byte{0xc0, 0x00, 0x02, 0x01}), 24)},   // Documentation network
			{"127.0.0.1/32", netip.PrefixFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), 32)},             // Localhost single host
			{"10.0.0.0/8", netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 0, 0, 0}), 8)},                 // Class A private network
			{"192.168.1.0/24", netip.PrefixFrom(netip.AddrFrom4([4]byte{192, 168, 1, 0}), 24)},         // Class C private network
			{"172.16.0.0/12", netip.PrefixFrom(netip.AddrFrom4([4]byte{172, 16, 0, 0}), 12)},           // Class B private network
			{"0.0.0.0/0", netip.PrefixFrom(netip.AddrFrom4([4]byte{0, 0, 0, 0}), 0)},                   // Default route
			{"255.255.255.255/32", netip.PrefixFrom(netip.AddrFrom4([4]byte{255, 255, 255, 255}), 32)}, // Broadcast single host
			{"192.168.1.1/30", netip.PrefixFrom(netip.AddrFrom4([4]byte{192, 168, 1, 1}), 30)},         // Point-to-point subnet
			// IPv6 CIDR notation
			{"fd7a:115c::626b:430b/118", netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfd, 0x7a, 0x11, 0x5c, 12: 0x62, 0x6b, 0x43, 0x0b}), 118)}, // ULA with specific prefix
			{"2001:db8::/32", netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x01, 0x0d, 0xb8}), 32)},                                         // Documentation network
			{"::1/128", netip.PrefixFrom(netip.AddrFrom16([16]byte{15: 1}), 128)},                                                               // IPv6 localhost single host
			{"::/0", netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0)},                                                                         // IPv6 default route
			{"fe80::/10", netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfe, 0x80}), 10)},                                                         // Link-local network
			{"2001:db8::1/64", netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x01, 0x0d, 0xb8, 12: 0, 0, 0, 1}), 64)},                        // Standard IPv6 subnet
			{"::ffff:0:0/96", netip.PrefixFrom(netip.AddrFrom16([16]byte{10: 0xff, 0xff}), 96)},                                                 // IPv4-mapped IPv6 prefix
		},
		fail: []decodeHookFailureTestCase[string, netip.Prefix]{
			{"5"},                // Just a number
			{"192.168.1.1"},      // Missing prefix length
			{"192.168.1.1/"},     // Empty prefix length
			{"192.168.1.1/33"},   // IPv4 prefix too large
			{"192.168.1.1/-1"},   // Negative prefix
			{"192.168.1.1/abc"},  // Non-numeric prefix
			{"256.1.1.1/24"},     // Invalid IP
			{"::1/129"},          // IPv6 prefix too large
			{"invalid::ip/64"},   // Invalid IPv6
			{""},                 // Empty string
			{"/24"},              // Missing IP
			{"192.168.1.1/24/8"}, // Multiple prefixes
		},
	}

	suite.Run(t)
}

func TestStringToBasicTypeHookFunc(t *testing.T) {
	strValue := reflect.ValueOf("42")

	cases := []struct {
		f, t   reflect.Value
		result any
		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) {
	suite := decodeHookTestSuite[string, int8]{
		fn: StringToInt8HookFunc(),
		ok: []decodeHookTestCase[string, int8]{
			{"42", 42},             // Basic positive decimal
			{"-42", int8(-42)},     // Basic negative decimal
			{"0b101010", int8(42)}, // Binary notation
			{"052", int8(42)},      // Octal notation (legacy)
			{"0o52", int8(42)},     // Octal notation (modern)
			{"0x2a", int8(42)},     // Hex notation (lowercase)
			{"0X2A", int8(42)},     // Hex notation (uppercase)
			{"0", int8(0)},         // Zero
			{"+42", int8(42)},      // Explicit positive sign
			// Boundary values
			{"127", int8(127)},          // Max value
			{"-128", int8(-128)},        // Min value
			{"0x7F", int8(127)},         // Max value in hex
			{"-0x80", int8(-128)},       // Min value in hex
			{"0177", int8(127)},         // Max value in octal
			{"-0200", int8(-128)},       // Min value in octal
			{"0b01111111", int8(127)},   // Max value in binary
			{"-0b10000000", int8(-128)}, // Min value in binary
			// Zero variants
			{"+0", int8(0)},  // Explicit positive zero
			{"-0", int8(0)},  // Explicit negative zero
			{"00", int8(0)},  // Leading zero
			{"0x0", int8(0)}, // Hex zero
			{"0b0", int8(0)}, // Binary zero
			{"0o0", int8(0)}, // Octal zero
		},
		fail: []decodeHookFailureTestCase[string, int8]{
			{strings.Repeat("42", 42)}, // Very long number string
			{"128"},                    // Overflow
			{"-129"},                   // Underflow
			{"256"},                    // Way over max
			{"-256"},                   // Way under min
			{"42.5"},                   // Float
			{"abc"},                    // Non-numeric
			{""},                       // Empty string
			{" 42 "},                   // Whitespace not handled by strconv
			{"\t42\n"},                 // Whitespace not handled by strconv
			{"\r42\r"},                 // Whitespace not handled by strconv
			{"0x"},                     // Invalid hex
			{"0b"},                     // Invalid binary
			{"0o"},                     // Invalid octal
			{"++42"},                   // Double plus
			{"--42"},                   // Double minus
			{"42abc"},                  // Trailing non-numeric
			{"abc42"},                  // Leading non-numeric
			{"42 43"},                  // Multiple numbers
			{"0x10000"},                // Hex overflow
			{"-0x10001"},               // Hex underflow
			{"0777"},                   // Octal overflow
			{"-01000"},                 // Octal underflow
			{"0b100000000"},            // Binary overflow
			{"-0b100000001"},           // Binary underflow
		},
	}

	suite.Run(t)
}

func TestStringToUint8HookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, uint8]{
		fn: StringToUint8HookFunc(),
		ok: []decodeHookTestCase[string, uint8]{
			{"42", 42},              // Basic decimal
			{"0b101010", uint8(42)}, // Binary notation
			{"052", uint8(42)},      // Octal notation (legacy)
			{"0o52", uint8(42)},     // Octal notation (modern)
			{"0x2a", uint8(42)},     // Hex notation (lowercase)
			{"0X2A", uint8(42)},     // Hex notation (uppercase)
			{"0", uint8(0)},         // Zero

			// Boundary values
			{"255", uint8(255)},        // Max value
			{"0xFF", uint8(255)},       // Max value in hex
			{"0377", uint8(255)},       // Max value in octal
			{"0b11111111", uint8(255)}, // Max value in binary
			{"1", uint8(1)},            // Min positive value
			// Zero variants

			{"00", uint8(0)},  // Leading zero
			{"0x0", uint8(0)}, // Hex zero
			{"0b0", uint8(0)}, // Binary zero
			{"0o0", uint8(0)}, // Octal zero
		},
		fail: []decodeHookFailureTestCase[string, uint8]{
			{strings.Repeat("42", 42)},
			{"256"},         // Overflow
			{"512"},         // Way over max
			{"42.5"},        // Float
			{"abc"},         // Non-numeric
			{""},            // Empty string
			{"-1"},          // Negative number
			{"-42"},         // Negative number
			{"0x"},          // Invalid hex
			{"0b"},          // Invalid binary
			{"0o"},          // Invalid octal
			{"++42"},        // Double plus
			{" 42 "},        // Whitespace not handled by strconv
			{"\t42\n"},      // Whitespace not handled by strconv
			{"\r42\r"},      // Whitespace not handled by strconv
			{"42abc"},       // Trailing non-numeric
			{"abc42"},       // Leading non-numeric
			{"42 43"},       // Multiple numbers
			{"0x100"},       // Hex overflow
			{"0400"},        // Octal overflow
			{"0b100000000"}, // Binary overflow
		},
	}

	suite.Run(t)
}

func TestStringToInt16HookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, int16]{
		fn: StringToInt16HookFunc(),
		ok: []decodeHookTestCase[string, int16]{
			{"42", 42},
			{"-42", int16(-42)},
			{"0b101010", int16(42)},
			{"052", int16(42)},
			{"0o52", int16(42)},
			{"0x2a", int16(42)},
			{"0X2A", int16(42)},
			{"0", int16(0)},
		},
		fail: []decodeHookFailureTestCase[string, int16]{
			{strings.Repeat("42", 42)},
			{"42.42"},
			{"0.0"},
		},
	}

	suite.Run(t)
}

func TestStringToUint16HookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, uint16]{
		fn: StringToUint16HookFunc(),
		ok: []decodeHookTestCase[string, uint16]{
			{"42", 42},
			{"0b101010", uint16(42)},
			{"052", uint16(42)},
			{"0o52", uint16(42)},
			{"0x2a", uint16(42)},
			{"0X2A", uint16(42)},
			{"0", uint16(0)},
		},
		fail: []decodeHookFailureTestCase[string, uint16]{
			{strings.Repeat("42", 42)},
			{"42.42"},
			{"-42"},
			{"0.0"},
		},
	}

	suite.Run(t)
}

func TestStringToInt32HookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, int32]{
		fn: StringToInt32HookFunc(),
		ok: []decodeHookTestCase[string, int32]{
			{"42", 42},
			{"-42", int32(-42)},
			{"0b101010", int32(42)},
			{"052", int32(42)},
			{"0o52", int32(42)},
			{"0x2a", int32(42)},
			{"0X2A", int32(42)},
			{"0", int32(0)},
		},
		fail: []decodeHookFailureTestCase[string, int32]{
			{strings.Repeat("42", 42)},
			{"42.42"},
			{"0.0"},
		},
	}

	suite.Run(t)
}

func TestStringToUint32HookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, uint32]{
		fn: StringToUint32HookFunc(),
		ok: []decodeHookTestCase[string, uint32]{
			{"42", 42},
			{"0b101010", uint32(42)},
			{"052", uint32(42)},
			{"0o52", uint32(42)},
			{"0x2a", uint32(42)},
			{"0X2A", uint32(42)},
			{"0", uint32(0)},
		},
		fail: []decodeHookFailureTestCase[string, uint32]{
			{strings.Repeat("42", 42)},
			{"42.42"},
			{"-42"},
			{"0.0"},
		},
	}

	suite.Run(t)
}

func TestStringToInt64HookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, int64]{
		fn: StringToInt64HookFunc(),
		ok: []decodeHookTestCase[string, int64]{
			{"42", 42},
			{"-42", int64(-42)},
			{"0b101010", int64(42)},
			{"052", int64(42)},
			{"0o52", int64(42)},
			{"0x2a", int64(42)},
			{"0X2A", int64(42)},
			{"0", int64(0)},
		},
		fail: []decodeHookFailureTestCase[string, int64]{
			{strings.Repeat("42", 42)},
			{"42.42"},
			{"0.0"},
		},
	}

	suite.Run(t)
}

func TestStringToUint64HookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, uint64]{
		fn: StringToUint64HookFunc(),
		ok: []decodeHookTestCase[string, uint64]{
			{"42", 42},
			{"0b101010", uint64(42)},
			{"052", uint64(42)},
			{"0o52", uint64(42)},
			{"0x2a", uint64(42)},
			{"0X2A", uint64(42)},
			{"0", uint64(0)},
		},
		fail: []decodeHookFailureTestCase[string, uint64]{
			{strings.Repeat("42", 42)},
			{"42.42"},
			{"-42"},
			{"0.0"},
		},
	}

	suite.Run(t)
}

func TestStringToIntHookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, int]{
		fn: StringToIntHookFunc(),
		ok: []decodeHookTestCase[string, int]{
			{"42", 42},
			{"-42", int(-42)},
			{"0b101010", int(42)},
			{"052", int(42)},
			{"0o52", int(42)},
			{"0x2a", int(42)},
			{"0X2A", int(42)},
			{"0", int(0)},
		},
		fail: []decodeHookFailureTestCase[string, int]{
			{strings.Repeat("42", 42)},
			{"42.42"},
			{"0.0"},
		},
	}

	suite.Run(t)
}

func TestStringToUintHookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, uint]{
		fn: StringToUintHookFunc(),
		ok: []decodeHookTestCase[string, uint]{
			{"42", 42},
			{"0b101010", uint(42)},
			{"052", uint(42)},
			{"0o52", uint(42)},
			{"0x2a", uint(42)},
			{"0X2A", uint(42)},
			{"0", uint(0)},
		},
		fail: []decodeHookFailureTestCase[string, uint]{
			{strings.Repeat("42", 42)},
			{"42.42"},
			{"-42"},
			{"0.0"},
		},
	}

	suite.Run(t)
}

func TestStringToFloat32HookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, float32]{
		fn: StringToFloat32HookFunc(),
		ok: []decodeHookTestCase[string, float32]{
			{"42.42", float32(42.42)},   // Basic decimal
			{"-42.42", float32(-42.42)}, // Negative decimal
			{"0", float32(0)},           // Zero as integer
			{"1e3", float32(1000)},      // Scientific notation
			{"1e-3", float32(0.001)},    // Small scientific notation
			// Integer values
			{"42", float32(42)},   // Positive integer
			{"-42", float32(-42)}, // Negative integer
			{"+42", float32(42)},  // Explicit positive integer
			// Zero variants
			{"0.0", float32(0.0)}, // Zero with decimal
			{"+0", float32(0)},    // Explicit positive zero
			{"-0", float32(0)},    // Explicit negative zero
			{"00.00", float32(0)}, // Zero with leading zeros
			// Scientific notation
			{"1E3", float32(1000)},        // Scientific notation (uppercase E)
			{"1.5e2", float32(150)},       // Fractional base with exponent
			{"1.5E2", float32(150)},       // Fractional base with uppercase E
			{"-1.5e2", float32(-150)},     // Negative fractional with exponent
			{"1e+3", float32(1000)},       // Explicit positive exponent
			{"1e-10", float32(1e-10)},     // Very small exponent
			{"3.14159", float32(3.14159)}, // Pi approximation
			// Special values - infinity
			{"inf", float32(math.Inf(1))},        // Infinity (lowercase)
			{"+inf", float32(math.Inf(1))},       // Positive infinity
			{"-inf", float32(math.Inf(-1))},      // Negative infinity
			{"Inf", float32(math.Inf(1))},        // Infinity (capitalized)
			{"+Inf", float32(math.Inf(1))},       // Positive infinity (capitalized)
			{"-Inf", float32(math.Inf(-1))},      // Negative infinity (capitalized)
			{"infinity", float32(math.Inf(1))},   // Infinity (full word)
			{"+infinity", float32(math.Inf(1))},  // Positive infinity (full word)
			{"-infinity", float32(math.Inf(-1))}, // Negative infinity (full word)
			{"Infinity", float32(math.Inf(1))},   // Infinity (full word capitalized)
			{"+Infinity", float32(math.Inf(1))},  // Positive infinity (full word capitalized)
			{"-Infinity", float32(math.Inf(-1))}, // Negative infinity (full word capitalized)
			// Decimal variations
			{".5", float32(0.5)},   // Leading decimal point
			{"-.5", float32(-0.5)}, // Negative leading decimal
			{"+.5", float32(0.5)},  // Positive leading decimal
			{"5.", float32(5.0)},   // Trailing decimal point
			{"-5.", float32(-5.0)}, // Negative trailing decimal
			{"+5.", float32(5.0)},  // Positive trailing decimal
			// Very small and large numbers
			{"1.1754943508222875e-38", float32(1.1754943508222875e-38)}, // Near min positive
			{"3.4028234663852886e+38", float32(3.4028234663852886e+38)}, // Near max

		},
		fail: []decodeHookFailureTestCase[string, float32]{
			{strings.Repeat("42", 420)},
			{"42.42.42"},
			{"abc"},      // Non-numeric
			{""},         // Empty string
			{"42abc"},    // Trailing non-numeric
			{"abc42"},    // Leading non-numeric
			{"42 43"},    // Multiple numbers
			{"++42"},     // Double plus
			{"--42"},     // Double minus
			{"1e"},       // Incomplete scientific notation
			{"1e+"},      // Incomplete scientific notation
			{"1e-"},      // Incomplete scientific notation
			{"1.2.3"},    // Multiple dots
			{"1..2"},     // Double dots
			{"."},        // Just a dot
			{" 42.5 "},   // Whitespace not handled by strconv
			{"\t42.5\n"}, // Whitespace not handled by strconv
			{"\r42.5\r"}, // Whitespace not handled by strconv
			{" 42.5 "},   // Whitespace not handled by strconv
			{"\t42.5\n"}, // Whitespace not handled by strconv
			{"\r42.5\r"}, // Whitespace not handled by strconv
			{"1e1e1"},    // Multiple exponents
			{"∞"},        // Unicode infinity
			{"NaΝ"},      // Unicode NaN lookalike
		},
	}

	// Custom test for NaN since NaN != NaN
	t.Run("NaN", func(t *testing.T) {
		f := StringToFloat32HookFunc()
		actual, err := DecodeHookExec(f, reflect.ValueOf("nan"), reflect.ValueOf(float32(0)))
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		if !math.IsNaN(float64(actual.(float32))) {
			t.Fatalf("expected NaN, got %v", actual)
		}
	})

	suite.Run(t)
}

func TestStringToFloat64HookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, float64]{
		fn: StringToFloat64HookFunc(),
		ok: []decodeHookTestCase[string, float64]{
			{"42.42", float64(42.42)},   // Basic decimal
			{"-42.42", float64(-42.42)}, // Negative decimal
			{"0", float64(0)},           // Zero as integer
			{"0.0", float64(0)},         // Zero with decimal
			{"1e3", float64(1000)},      // Scientific notation
			{"1e-3", float64(0.001)},    // Small scientific notation
			// Integer values
			{"42", float64(42)},   // Positive integer
			{"-42", float64(-42)}, // Negative integer
			{"+42", float64(42)},  // Explicit positive integer
			// Zero variants
			{"+0", float64(0)},    // Explicit positive zero
			{"-0", float64(0)},    // Explicit negative zero
			{"00.00", float64(0)}, // Zero with leading zeros
			// Scientific notation
			{"1E3", float64(1000)},                            // Scientific notation (uppercase E)
			{"1.5e2", float64(150)},                           // Fractional base with exponent
			{"1.5E2", float64(150)},                           // Fractional base with uppercase E
			{"-1.5e2", float64(-150)},                         // Negative fractional with exponent
			{"1e+3", float64(1000)},                           // Explicit positive exponent
			{"1e-15", float64(1e-15)},                         // Very small exponent
			{"3.141592653589793", float64(3.141592653589793)}, // Pi with high precision
			// Special values - infinity
			{"inf", math.Inf(1)},        // Infinity (lowercase)
			{"+inf", math.Inf(1)},       // Positive infinity
			{"-inf", math.Inf(-1)},      // Negative infinity
			{"Inf", math.Inf(1)},        // Infinity (capitalized)
			{"+Inf", math.Inf(1)},       // Positive infinity (capitalized)
			{"-Inf", math.Inf(-1)},      // Negative infinity (capitalized)
			{"infinity", math.Inf(1)},   // Infinity (full word)
			{"+infinity", math.Inf(1)},  // Positive infinity (full word)
			{"-infinity", math.Inf(-1)}, // Negative infinity (full word)
			{"Infinity", math.Inf(1)},   // Infinity (full word capitalized)
			{"+Infinity", math.Inf(1)},  // Positive infinity (full word capitalized)
			{"-Infinity", math.Inf(-1)}, // Negative infinity (full word capitalized)
			// Decimal variations
			{".5", float64(0.5)},   // Leading decimal point
			{"-.5", float64(-0.5)}, // Negative leading decimal
			{"+.5", float64(0.5)},  // Positive leading decimal
			{"5.", float64(5.0)},   // Trailing decimal point
			{"-5.", float64(-5.0)}, // Negative trailing decimal
			{"+5.", float64(5.0)},  // Positive trailing decimal
			// Very small and large numbers
			{"2.2250738585072014e-308", float64(2.2250738585072014e-308)}, // Near min positive
			{"1.7976931348623157e+308", float64(1.7976931348623157e+308)}, // Near max
			{"4.9406564584124654e-324", float64(4.9406564584124654e-324)}, // Min positive subnormal

		},
		fail: []decodeHookFailureTestCase[string, float64]{
			{strings.Repeat("42", 420)},
			{"42.42.42"},
			{"abc"},   // Non-numeric
			{""},      // Empty string
			{"42abc"}, // Trailing non-numeric
			{"abc42"}, // Leading non-numeric
			{"42 43"}, // Multiple numbers
			{"++42"},  // Double plus
			{"--42"},  // Double minus
			{"1e"},    // Incomplete scientific notation
			{"1e+"},   // Incomplete scientific notation
			{"1e-"},   // Incomplete scientific notation
			{"1.2.3"}, // Multiple dots
			{"1..2"},  // Double dots
			{"."},     // Just a dot
			{"1e1e1"}, // Multiple exponents
			{"∞"},     // Unicode infinity
			{"NaΝ"},   // Unicode NaN lookalike
		},
	}

	// Custom test for NaN since NaN != NaN
	t.Run("NaN", func(t *testing.T) {
		f := StringToFloat64HookFunc()
		actual, err := DecodeHookExec(f, reflect.ValueOf("nan"), reflect.ValueOf(float64(0)))
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		if !math.IsNaN(actual.(float64)) {
			t.Fatalf("expected NaN, got %v", actual)
		}
	})

	suite.Run(t)
}

func TestStringToComplex64HookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, complex64]{
		fn: StringToComplex64HookFunc(),
		ok: []decodeHookTestCase[string, complex64]{
			// Standard complex numbers
			{"42.42+42.42i", complex(float32(42.42), float32(42.42))}, // Basic complex number
			{"1+2i", complex(float32(1), float32(2))},                 // Simple complex number
			{"-1-2i", complex(float32(-1), float32(-2))},              // Negative real and imaginary
			{"1-2i", complex(float32(1), float32(-2))},                // Positive real, negative imaginary
			{"-1+2i", complex(float32(-1), float32(2))},               // Negative real, positive imaginary
			// Real numbers only
			{"-42.42", complex(float32(-42.42), 0)}, // Negative real number
			{"42", complex(float32(42), 0)},         // Positive integer
			{"+42", complex(float32(42), 0)},        // Explicit positive integer
			{"0", complex(float32(0), 0)},           // Zero
			{"0.0", complex(float32(0), 0)},         // Zero with decimal
			{"+0", complex(float32(0), 0)},          // Explicit positive zero
			{"-0", complex(float32(0), 0)},          // Explicit negative zero
			// Scientific notation
			{"1e3", complex(float32(1000), 0)},    // Scientific notation
			{"1e-3", complex(float32(0.001), 0)},  // Small scientific notation
			{"1E3", complex(float32(1000), 0)},    // Uppercase E
			{"1e+3", complex(float32(1000), 0)},   // Explicit positive exponent
			{"1.5e2", complex(float32(150), 0)},   // Fractional with exponent
			{"-1.5e2", complex(float32(-150), 0)}, // Negative fractional with exponent
			// Imaginary numbers only
			{"1e3i", complex(float32(0), 1000)},   // Scientific notation imaginary
			{"1e-3i", complex(float32(0), 0.001)}, // Small scientific notation imaginary
			{"42i", complex(float32(0), 42)},      // Basic imaginary
			{"-42i", complex(float32(0), -42)},    // Negative imaginary
			{"+42i", complex(float32(0), 42)},     // Explicit positive imaginary
			{"0i", complex(float32(0), 0)},        // Zero imaginary
			{"1i", complex(float32(0), 1)},        // Unit imaginary
			{"-1i", complex(float32(0), -1)},      // Negative unit imaginary
			{"1.5i", complex(float32(0), 1.5)},    // Fractional imaginary
			// Scientific notation imaginary
			{"1E3i", complex(float32(0), 1000)},    // Uppercase E imaginary
			{"1e+3i", complex(float32(0), 1000)},   // Explicit positive exponent imaginary
			{"1.5e2i", complex(float32(0), 150)},   // Fractional with exponent imaginary
			{"-1.5e2i", complex(float32(0), -150)}, // Negative fractional with exponent imaginary
			// Complex with scientific notation
			{"1e3+2e2i", complex(float32(1000), float32(200))},     // Both parts scientific
			{"1e-3+2e-2i", complex(float32(0.001), float32(0.02))}, // Both parts small scientific
			{"1.5e2-2.5e1i", complex(float32(150), float32(-25))},  // Mixed signs with scientific
			// Decimal variations
			{".5", complex(float32(0.5), 0)},    // Leading decimal point
			{"-.5", complex(float32(-0.5), 0)},  // Negative leading decimal
			{"+.5", complex(float32(0.5), 0)},   // Positive leading decimal
			{"5.", complex(float32(5.0), 0)},    // Trailing decimal point
			{"-5.", complex(float32(-5.0), 0)},  // Negative trailing decimal
			{"+5.", complex(float32(5.0), 0)},   // Positive trailing decimal
			{".5i", complex(float32(0), 0.5)},   // Leading decimal imaginary
			{"-.5i", complex(float32(0), -0.5)}, // Negative leading decimal imaginary
			{"+.5i", complex(float32(0), 0.5)},  // Positive leading decimal imaginary
			{"5.i", complex(float32(0), 5.0)},   // Trailing decimal imaginary
			{"-5.i", complex(float32(0), -5.0)}, // Negative trailing decimal imaginary
			{"+5.i", complex(float32(0), 5.0)},  // Positive trailing decimal imaginary
			// Complex decimal variations
			{".5+.5i", complex(float32(0.5), float32(0.5))},  // Both parts leading decimal
			{"5.+5.i", complex(float32(5.0), float32(5.0))},  // Both parts trailing decimal
			{".5-.5i", complex(float32(0.5), float32(-0.5))}, // Leading decimal with negative
			// Special values - infinity
			{"inf", complex(float32(math.Inf(1)), 0)},                // Real infinity
			{"+inf", complex(float32(math.Inf(1)), 0)},               // Positive real infinity
			{"-inf", complex(float32(math.Inf(-1)), 0)},              // Negative real infinity
			{"Inf", complex(float32(math.Inf(1)), 0)},                // Capitalized infinity
			{"infinity", complex(float32(math.Inf(1)), 0)},           // Full word infinity
			{"Infinity", complex(float32(math.Inf(1)), 0)},           // Capitalized full word infinity
			{"infi", complex(float32(0), float32(math.Inf(1)))},      // Imaginary infinity
			{"+infi", complex(float32(0), float32(math.Inf(1)))},     // Positive imaginary infinity
			{"-infi", complex(float32(0), float32(math.Inf(-1)))},    // Negative imaginary infinity
			{"Infi", complex(float32(0), float32(math.Inf(1)))},      // Capitalized imaginary infinity
			{"infinityi", complex(float32(0), float32(math.Inf(1)))}, // Full word imaginary infinity
			{"Infinityi", complex(float32(0), float32(math.Inf(1)))}, // Capitalized full word imaginary infinity
			// Complex with special values
			{"inf+1i", complex(float32(math.Inf(1)), float32(1))},                // Real infinity with imaginary
			{"1+infi", complex(float32(1), float32(math.Inf(1)))},                // Real with imaginary infinity
			{"inf+infi", complex(float32(math.Inf(1)), float32(math.Inf(1)))},    // Both infinities
			{"-inf-infi", complex(float32(math.Inf(-1)), float32(math.Inf(-1)))}, // Both negative infinities
			// Parentheses format
			{"(42+42i)", complex(float32(42), float32(42))},    // Complex in parentheses
			{"(42)", complex(float32(42), float32(0))},         // Real in parentheses
			{"(42i)", complex(float32(0), float32(42))},        // Imaginary in parentheses
			{"(-42-42i)", complex(float32(-42), float32(-42))}, // Negative complex in parentheses
		},
		fail: []decodeHookFailureTestCase[string, complex64]{
			{strings.Repeat("42", 420)},
			{"42.42.42"},
			{"abc"},            // Non-numeric
			{""},               // Empty string
			{"42abc"},          // Trailing non-numeric
			{"abc42"},          // Leading non-numeric
			{"42+abc"},         // Invalid imaginary part
			{"abc+42i"},        // Invalid real part
			{"42++42i"},        // Double plus
			{"42+-+42i"},       // Multiple signs
			{"42+42j"},         // Wrong imaginary unit
			{"42+42k"},         // Wrong imaginary unit
			{"42 + 42i"},       // Spaces around operator
			{"42+42i+1"},       // Extra components
			{"42+42i+1i"},      // Multiple imaginary parts
			{"42+42i+1+2i"},    // Too many components
			{"(42+42i"},        // Unclosed parenthesis
			{"42+42i)"},        // Extra closing parenthesis
			{"((42+42i))"},     // Double parentheses
			{"(42+42i)(1+1i)"}, // Multiple complex numbers
			{"42i+42"},         // Imaginary first (not standard)
			{"i"},              // Just 'i'
			{"42.42.42+1i"},    // Invalid real part
			{"42+42.42.42i"},   // Invalid imaginary part
			{"1e"},             // Incomplete scientific notation
			{"1e+"},            // Incomplete scientific notation
			{"1e-"},            // Incomplete scientific notation
			{"1e+i"},           // Incomplete scientific notation
			{"1.2.3+1i"},       // Multiple dots in real
			{"1+1.2.3i"},       // Multiple dots in imaginary
			{"1..2+1i"},        // Double dots in real
			{"1+1..2i"},        // Double dots in imaginary
			{".+.i"},           // Just dots
			{"1e1e1+1i"},       // Multiple exponents in real
			{" 42+42i "},       // Whitespace not handled by strconv
			{"\t42i\n"},        // Whitespace not handled by strconv
			{"\r42\r"},         // Whitespace not handled by strconv
			{" 42+42i "},       // Whitespace not handled by strconv
			{"\t42i\n"},        // Whitespace not handled by strconv
			{"\r42\r"},         // Whitespace not handled by strconv
			{"1+1e1e1i"},       // Multiple exponents in imaginary
			{"∞"},              // Unicode infinity
			{"∞+∞i"},           // Unicode infinity complex
			{"NaΝ"},            // Unicode NaN lookalike
		},
	}

	// Custom test for NaN since NaN != NaN
	t.Run("NaN", func(t *testing.T) {
		f := StringToComplex64HookFunc()

		// Test real NaN
		actual, err := DecodeHookExec(f, reflect.ValueOf("nan"), reflect.ValueOf(complex64(0)))
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		c := actual.(complex64)
		if !math.IsNaN(float64(real(c))) || imag(c) != 0 {
			t.Fatalf("expected NaN+0i, got %v", c)
		}

		// Test imaginary NaN
		actual, err = DecodeHookExec(f, reflect.ValueOf("nani"), reflect.ValueOf(complex64(0)))
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		c = actual.(complex64)
		if real(c) != 0 || !math.IsNaN(float64(imag(c))) {
			t.Fatalf("expected 0+NaNi, got %v", c)
		}

		// Test complex NaN
		actual, err = DecodeHookExec(f, reflect.ValueOf("nan+nani"), reflect.ValueOf(complex64(0)))
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		c = actual.(complex64)
		if !math.IsNaN(float64(real(c))) || !math.IsNaN(float64(imag(c))) {
			t.Fatalf("expected NaN+NaNi, got %v", c)
		}
	})

	suite.Run(t)
}

func TestStringToBoolHookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, bool]{
		fn: StringToBoolHookFunc(),
		ok: []decodeHookTestCase[string, bool]{
			// True values (only those accepted by strconv.ParseBool)
			{"true", true}, // Boolean true (lowercase)
			{"True", true}, // Boolean true (capitalized)
			{"TRUE", true}, // Boolean true (uppercase)
			{"t", true},    // Single character true (lowercase)
			{"T", true},    // Single character true (uppercase)
			{"1", true},    // Numeric true

			// False values (only those accepted by strconv.ParseBool)
			{"false", false}, // Boolean false (lowercase)
			{"False", false}, // Boolean false (capitalized)
			{"FALSE", false}, // Boolean false (uppercase)
			{"f", false},     // Single character false (lowercase)
			{"F", false},     // Single character false (uppercase)
			{"0", false},     // Numeric false
		},
		fail: []decodeHookFailureTestCase[string, bool]{
			{""},           // Empty string
			{"maybe"},      // Invalid boolean word
			{"yes"},        // Not accepted by strconv.ParseBool
			{"no"},         // Not accepted by strconv.ParseBool
			{"on"},         // Not accepted by strconv.ParseBool
			{"off"},        // Not accepted by strconv.ParseBool
			{"y"},          // Not accepted by strconv.ParseBool
			{"n"},          // Not accepted by strconv.ParseBool
			{"yes please"}, // Invalid boolean phrase
			{"true false"}, // Multiple boolean values
			{"2"},          // Invalid number (only 0/1 accepted)
			{"-1"},         // Negative number
			{"10"},         // Number greater than 1
			{"abc"},        // Non-boolean text
			{"True False"}, // Multiple boolean values (capitalized)
			{"1.0"},        // Float representation of 1
			{"0.0"},        // Float representation of 0
			{"++true"},     // Double positive prefix
			{"--false"},    // Double negative prefix
			{"truee"},      // Typo in true
			{"fasle"},      // Typo in false
			{"tru"},        // Incomplete true
			{"fals"},       // Incomplete false
			{" true "},     // Whitespace not handled by strconv.ParseBool
			{"\ttrue\n"},   // Tab and newline whitespace
			{"\rfalse\r"},  // Carriage return whitespace
			{" 1 "},        // Whitespace around numeric true
			{" 0 "},        // Whitespace around numeric false
			{"∞"},          // Unicode infinity symbol
			{"тrue"},       // Cyrillic lookalike characters
		},
	}

	// Test non-string and non-bool type passthrough
	t.Run("Passthrough", func(t *testing.T) {
		f := StringToBoolHookFunc()

		// Non-string type should pass through
		intValue := reflect.ValueOf(42)
		actual, err := DecodeHookExec(f, intValue, reflect.ValueOf(false))
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		if actual != 42 {
			t.Fatalf("expected 42, got %v", actual)
		}

		// Non-bool target type should pass through
		strValue := reflect.ValueOf("true")
		actual, err = DecodeHookExec(f, strValue, strValue)
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		if actual != "true" {
			t.Fatalf("expected 'true', got %v", actual)
		}
	})

	suite.Run(t)
}

func TestStringToComplex128HookFunc(t *testing.T) {
	suite := decodeHookTestSuite[string, complex128]{
		fn: StringToComplex128HookFunc(),
		ok: []decodeHookTestCase[string, complex128]{
			// Standard complex numbers
			{"42.42+42.42i", complex(42.42, 42.42)}, // Basic complex number
			{"1+2i", complex(1, 2)},                 // Simple complex number
			{"-1-2i", complex(-1, -2)},              // Negative real and imaginary
			{"1-2i", complex(1, -2)},                // Positive real, negative imaginary
			{"-1+2i", complex(-1, 2)},               // Negative real, positive imaginary
			// Real numbers only
			{"-42.42", complex(-42.42, 0)}, // Negative real number
			{"42", complex(42, 0)},         // Positive integer
			{"+42", complex(42, 0)},        // Explicit positive integer
			{"0", complex(0, 0)},           // Zero
			{"0.0", complex(0, 0)},         // Zero with decimal
			{"+0", complex(0, 0)},          // Explicit positive zero
			{"-0", complex(0, 0)},          // Explicit negative zero
			// Scientific notation
			{"1e3", complex(1000, 0)},                            // Scientific notation
			{"1e-3", complex(0.001, 0)},                          // Small scientific notation
			{"1E3", complex(1000, 0)},                            // Uppercase E
			{"1e+3", complex(1000, 0)},                           // Explicit positive exponent
			{"1.5e2", complex(150, 0)},                           // Fractional with exponent
			{"-1.5e2", complex(-150, 0)},                         // Negative fractional with exponent
			{"1e-15", complex(1e-15, 0)},                         // Very small scientific notation
			{"3.141592653589793", complex(3.141592653589793, 0)}, // Pi with high precision
			// Imaginary numbers only
			{"1e3i", complex(0, 1000)},   // Scientific notation imaginary
			{"1e-3i", complex(0, 0.001)}, // Small scientific notation imaginary
			{"42i", complex(0, 42)},      // Basic imaginary
			{"-42i", complex(0, -42)},    // Negative imaginary
			{"+42i", complex(0, 42)},     // Explicit positive imaginary
			{"0i", complex(0, 0)},        // Zero imaginary
			{"1i", complex(0, 1)},        // Unit imaginary
			{"-1i", complex(0, -1)},      // Negative unit imaginary
			{"1.5i", complex(0, 1.5)},    // Fractional imaginary
			// Scientific notation imaginary
			{"1E3i", complex(0, 1000)},    // Uppercase E imaginary
			{"1e+3i", complex(0, 1000)},   // Explicit positive exponent imaginary
			{"1.5e2i", complex(0, 150)},   // Fractional with exponent imaginary
			{"-1.5e2i", complex(0, -150)}, // Negative fractional with exponent imaginary
			{"1e-15i", complex(0, 1e-15)}, // Very small scientific notation imaginary
			// Complex with scientific notation
			{"1e3+2e2i", complex(1000, 200)},        // Both parts scientific
			{"1e-3+2e-2i", complex(0.001, 0.02)},    // Both parts small scientific
			{"1.5e2-2.5e1i", complex(150, -25)},     // Mixed signs with scientific
			{"1e-15+1e-15i", complex(1e-15, 1e-15)}, // Both parts very small scientific
			// Decimal variations
			{".5", complex(0.5, 0)},    // Leading decimal point
			{"-.5", complex(-0.5, 0)},  // Negative leading decimal
			{"+.5", complex(0.5, 0)},   // Positive leading decimal
			{"5.", complex(5.0, 0)},    // Trailing decimal point
			{"-5.", complex(-5.0, 0)},  // Negative trailing decimal
			{"+5.", complex(5.0, 0)},   // Positive trailing decimal
			{".5i", complex(0, 0.5)},   // Leading decimal imaginary
			{"-.5i", complex(0, -0.5)}, // Negative leading decimal imaginary
			{"+.5i", complex(0, 0.5)},  // Positive leading decimal imaginary
			{"5.i", complex(0, 5.0)},   // Trailing decimal imaginary
			{"-5.i", complex(0, -5.0)}, // Negative trailing decimal imaginary
			{"+5.i", complex(0, 5.0)},  // Positive trailing decimal imaginary
			// Complex decimal variations
			{".5+.5i", complex(0.5, 0.5)},  // Both parts leading decimal
			{"5.+5.i", complex(5.0, 5.0)},  // Both parts trailing decimal
			{".5-.5i", complex(0.5, -0.5)}, // Leading decimal with negative
			// Very small and large numbers
			{"2.2250738585072014e-308", complex(2.2250738585072014e-308, 0)},  // Near min positive real
			{"1.7976931348623157e+308", complex(1.7976931348623157e+308, 0)},  // Near max real
			{"4.9406564584124654e-324", complex(4.9406564584124654e-324, 0)},  // Min positive subnormal real
			{"2.2250738585072014e-308i", complex(0, 2.2250738585072014e-308)}, // Near min positive imaginary
			{"1.7976931348623157e+308i", complex(0, 1.7976931348623157e+308)}, // Near max imaginary
			{"4.9406564584124654e-324i", complex(0, 4.9406564584124654e-324)}, // Min positive subnormal imaginary
			// Special values - infinity
			{"inf", complex(math.Inf(1), 0)},       // Real infinity
			{"+inf", complex(math.Inf(1), 0)},      // Positive real infinity
			{"-inf", complex(math.Inf(-1), 0)},     // Negative real infinity
			{"Inf", complex(math.Inf(1), 0)},       // Capitalized infinity
			{"infinity", complex(math.Inf(1), 0)},  // Full word infinity
			{"Infinity", complex(math.Inf(1), 0)},  // Capitalized full word infinity
			{"infi", complex(0, math.Inf(1))},      // Imaginary infinity
			{"+infi", complex(0, math.Inf(1))},     // Positive imaginary infinity
			{"-infi", complex(0, math.Inf(-1))},    // Negative imaginary infinity
			{"Infi", complex(0, math.Inf(1))},      // Capitalized imaginary infinity
			{"infinityi", complex(0, math.Inf(1))}, // Full word imaginary infinity
			{"Infinityi", complex(0, math.Inf(1))}, // Capitalized full word imaginary infinity
			// Complex with special values
			{"inf+1i", complex(math.Inf(1), 1)},                // Real infinity with imaginary
			{"1+infi", complex(1, math.Inf(1))},                // Real with imaginary infinity
			{"inf+infi", complex(math.Inf(1), math.Inf(1))},    // Both infinities
			{"-inf-infi", complex(math.Inf(-1), math.Inf(-1))}, // Both negative infinities
			// Parentheses format
			{"(42+42i)", complex(42, 42)},    // Complex in parentheses
			{"(42)", complex(42, 0)},         // Real in parentheses
			{"(42i)", complex(0, 42)},        // Imaginary in parentheses
			{"(-42-42i)", complex(-42, -42)}, // Negative complex in parentheses
		},
		fail: []decodeHookFailureTestCase[string, complex128]{
			{strings.Repeat("42", 420)},
			{"42.42.42"},
			{"abc"},            // Non-numeric
			{""},               // Empty string
			{"42abc"},          // Trailing non-numeric
			{"abc42"},          // Leading non-numeric
			{"42+abc"},         // Invalid imaginary part
			{"abc+42i"},        // Invalid real part
			{"42++42i"},        // Double plus
			{"42+-+42i"},       // Multiple signs
			{"42+42j"},         // Wrong imaginary unit
			{"42+42k"},         // Wrong imaginary unit
			{"42 + 42i"},       // Spaces around operator
			{"42+42i+1"},       // Extra components
			{"42+42i+1i"},      // Multiple imaginary parts
			{"42+42i+1+2i"},    // Too many components
			{"(42+42i"},        // Unclosed parenthesis
			{"42+42i)"},        // Extra closing parenthesis
			{"((42+42i))"},     // Double parentheses
			{"(42+42i)(1+1i)"}, // Multiple complex numbers
			{"42i+42"},         // Imaginary first (not standard)
			{"i"},              // Just 'i'
			{"42.42.42+1i"},    // Invalid real part
			{"42+42.42.42i"},   // Invalid imaginary part
			{"1e"},             // Incomplete scientific notation
			{"1e+"},            // Incomplete scientific notation
			{"1e-"},            // Incomplete scientific notation
			{"1e+i"},           // Incomplete scientific notation
			{"1.2.3+1i"},       // Multiple dots in real
			{"1+1.2.3i"},       // Multiple dots in imaginary
			{"1..2+1i"},        // Double dots in real
			{"1+1..2i"},        // Double dots in imaginary
			{".+.i"},           // Just dots
			{"1e1e1+1i"},       // Multiple exponents in real
			{"1+1e1e1i"},       // Multiple exponents in imaginary
			{"∞"},              // Unicode infinity
			{"∞+∞i"},           // Unicode infinity complex
			{"NaΝ"},            // Unicode NaN lookalike
		},
	}

	// Custom test for NaN since NaN != NaN
	t.Run("NaN", func(t *testing.T) {
		f := StringToComplex128HookFunc()

		// Test real NaN
		actual, err := DecodeHookExec(f, reflect.ValueOf("nan"), reflect.ValueOf(complex128(0)))
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		c := actual.(complex128)
		if !math.IsNaN(real(c)) || imag(c) != 0 {
			t.Fatalf("expected NaN+0i, got %v", c)
		}

		// Test imaginary NaN
		actual, err = DecodeHookExec(f, reflect.ValueOf("nani"), reflect.ValueOf(complex128(0)))
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		c = actual.(complex128)
		if real(c) != 0 || !math.IsNaN(imag(c)) {
			t.Fatalf("expected 0+NaNi, got %v", c)
		}

		// Test complex NaN
		actual, err = DecodeHookExec(f, reflect.ValueOf("nan+nani"), reflect.ValueOf(complex128(0)))
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		c = actual.(complex128)
		if !math.IsNaN(real(c)) || !math.IsNaN(imag(c)) {
			t.Fatalf("expected NaN+NaNi, got %v", c)
		}
	})

	suite.Run(t)
}

func TestErrorLeakageDecodeHook(t *testing.T) {
	cases := []struct {
		value         any
		target        any
		hook          DecodeHookFunc
		allowNilError bool
	}{
		// case 0
		{1234, []string{}, StringToSliceHookFunc(","), true},
		{"testing", time.Second, StringToTimeDurationHookFunc(), false},
		{":testing", &url.URL{}, StringToURLHookFunc(), false},
		{"testing", net.IP{}, StringToIPHookFunc(), false},
		{"testing", net.IPNet{}, StringToIPNetHookFunc(), false},
		// case 5
		{"testing", time.Time{}, StringToTimeHookFunc(time.RFC3339), false},
		{"testing", time.Time{}, StringToTimeHookFunc(time.RFC3339), false},
		{true, true, WeaklyTypedHook, true},
		{true, "string", WeaklyTypedHook, true},
		{1.0, "string", WeaklyTypedHook, true},
		// case 10
		{1, "string", WeaklyTypedHook, true},
		{[]uint8{0x00}, "string", WeaklyTypedHook, true},
		{uint(0), "string", WeaklyTypedHook, true},
		{struct{}{}, struct{}{}, RecursiveStructToMapHookFunc(), true},
		{"testing", netip.Addr{}, StringToNetIPAddrHookFunc(), false},
		// case 15
		{"testing:testing", netip.AddrPort{}, StringToNetIPAddrPortHookFunc(), false},
		{"testing", netip.Prefix{}, StringToNetIPPrefixHookFunc(), false},
		{"testing", int8(0), StringToInt8HookFunc(), false},
		{"testing", uint8(0), StringToUint8HookFunc(), false},
		// case 20
		{"testing", int16(0), StringToInt16HookFunc(), false},
		{"testing", uint16(0), StringToUint16HookFunc(), false},
		{"testing", int32(0), StringToInt32HookFunc(), false},
		{"testing", uint32(0), StringToUint32HookFunc(), false},
		{"testing", int64(0), StringToInt64HookFunc(), false},
		// case 25
		{"testing", uint64(0), StringToUint64HookFunc(), false},
		{"testing", int(0), StringToIntHookFunc(), false},
		{"testing", uint(0), StringToUintHookFunc(), false},
		{"testing", float32(0), StringToFloat32HookFunc(), false},
		{"testing", float64(0), StringToFloat64HookFunc(), false},
		// case 30
		{"testing", true, StringToBoolHookFunc(), false},
		{"testing", byte(0), StringToByteHookFunc(), false},
		{"testing", rune(0), StringToRuneHookFunc(), false},
		{"testing", complex64(0), StringToComplex64HookFunc(), false},
		{"testing", complex128(0), StringToComplex128HookFunc(), false},
	}

	for i, tc := range cases {
		value := reflect.ValueOf(tc.value)
		target := reflect.ValueOf(tc.target)
		output, err := DecodeHookExec(tc.hook, value, target)

		if err == nil {
			if tc.allowNilError {
				continue
			}

			t.Fatalf("case %d: expected error from input %v:\n\toutput (%T): %#v\n\toutput (string): %v", i, tc.value, output, output, output)
		}

		strValue := fmt.Sprintf("%v", tc.value)
		if strings.Contains(err.Error(), strValue) {
			t.Errorf("case %d: error contains input value\n\terr: %v\n\tinput: %v", i, err, strValue)
		} else {
			t.Logf("case %d: got safe error: %v", i, err)
		}
	}
}


================================================
FILE: devenv.nix
================================================
{
  pkgs,
  ...
}:

{
  languages = {
    go.enable = true;
  };

  packages = with pkgs; [
    golangci-lint
  ];
}


================================================
FILE: devenv.yaml
================================================
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
inputs:
  nixpkgs:
    url: github:cachix/devenv-nixpkgs/rolling


================================================
FILE: errors.go
================================================
package mapstructure

import (
	"errors"
	"fmt"
	"net"
	"net/url"
	"reflect"
	"strconv"
	"strings"
	"time"
)

// Error interface is implemented by all errors emitted by mapstructure.
//
// Use [errors.As] to check if an error implements this interface.
type Error interface {
	error

	mapstructure()
}

// DecodeError is a generic error type that holds information about
// a decoding error together with the name of the field that caused the error.
type DecodeError struct {
	name string
	err  error
}

func newDecodeError(name string, err error) *DecodeError {
	return &DecodeError{
		name: name,
		err:  err,
	}
}

func (e *DecodeError) Name() string {
	return e.name
}

func (e *DecodeError) Unwrap() error {
	return e.err
}

func (e *DecodeError) Error() string {
	return fmt.Sprintf("'%s' %s", e.name, e.err)
}

func (*DecodeError) mapstructure() {}

// ParseError is an error type that indicates a value could not be parsed
// into the expected type.
type ParseError struct {
	Expected reflect.Value
	Value    any
	Err      error
}

func (e *ParseError) Error() string {
	return fmt.Sprintf("cannot parse value as '%s': %s", e.Expected.Type(), e.Err)
}

func (*ParseError) mapstructure() {}

// UnconvertibleTypeError is an error type that indicates a value could not be
// converted to the expected type.
type UnconvertibleTypeError struct {
	Expected reflect.Value
	Value    any
}

func (e *UnconvertibleTypeError) Error() string {
	return fmt.Sprintf(
		"expected type '%s', got unconvertible type '%s'",
		e.Expected.Type(),
		reflect.TypeOf(e.Value),
	)
}

func (*UnconvertibleTypeError) mapstructure() {}

func wrapStrconvNumError(err error) error {
	if err == nil {
		return nil
	}

	if err, ok := err.(*strconv.NumError); ok {
		return &strconvNumError{Err: err}
	}

	return err
}

type strconvNumError struct {
	Err *strconv.NumError
}

func (e *strconvNumError) Error() string {
	return "strconv." + e.Err.Func + ": " + e.Err.Err.Error()
}

func (e *strconvNumError) Unwrap() error { return e.Err }

func wrapUrlError(err error) error {
	if err == nil {
		return nil
	}

	if err, ok := err.(*url.Error); ok {
		return &urlError{Err: err}
	}

	return err
}

type urlError struct {
	Err *url.Error
}

func (e *urlError) Error() string {
	return fmt.Sprintf("%s", e.Err.Err)
}

func (e *urlError) Unwrap() error { return e.Err }

func wrapNetParseError(err error) error {
	if err == nil {
		return nil
	}

	if err, ok := err.(*net.ParseError); ok {
		return &netParseError{Err: err}
	}

	return err
}

type netParseError struct {
	Err *net.ParseError
}

func (e *netParseError) Error() string {
	return "invalid " + e.Err.Type
}

func (e *netParseError) Unwrap() error { return e.Err }

func wrapTimeParseError(err error) error {
	if err == nil {
		return nil
	}

	if err, ok := err.(*time.ParseError); ok {
		return &timeParseError{Err: err}
	}

	return err
}

type timeParseError struct {
	Err *time.ParseError
}

func (e *timeParseError) Error() string {
	if e.Err.Message == "" {
		return fmt.Sprintf("parsing time as %q: cannot parse as %q", e.Err.Layout, e.Err.LayoutElem)
	}

	return "parsing time " + e.Err.Message
}

func (e *timeParseError) Unwrap() error { return e.Err }

func wrapNetIPParseAddrError(err error) error {
	if err == nil {
		return nil
	}

	if errMsg := err.Error(); strings.HasPrefix(errMsg, "ParseAddr") {
		errPieces := strings.Split(errMsg, ": ")

		return fmt.Errorf("ParseAddr: %s", errPieces[len(errPieces)-1])
	}

	return err
}

func wrapNetIPParseAddrPortError(err error) error {
	if err == nil {
		return nil
	}

	errMsg := err.Error()
	if strings.HasPrefix(errMsg, "invalid port ") {
		return errors.New("invalid port")
	} else if strings.HasPrefix(errMsg, "invalid ip:port ") {
		return errors.New("invalid ip:port")
	}

	return err
}

func wrapNetIPParsePrefixError(err error) error {
	if err == nil {
		return nil
	}

	if errMsg := err.Error(); strings.HasPrefix(errMsg, "netip.ParsePrefix") {
		errPieces := strings.Split(errMsg, ": ")

		return fmt.Errorf("netip.ParsePrefix: %s", errPieces[len(errPieces)-1])
	}

	return err
}

func wrapTimeParseDurationError(err error) error {
	if err == nil {
		return nil
	}

	errMsg := err.Error()
	if strings.HasPrefix(errMsg, "time: unknown unit ") {
		return errors.New("time: unknown unit")
	} else if strings.HasPrefix(errMsg, "time: ") {
		idx := strings.LastIndex(errMsg, " ")

		return errors.New(errMsg[:idx])
	}

	return err
}

func wrapTimeParseLocationError(err error) error {
	if err == nil {
		return nil
	}
	errMsg := err.Error()
	if strings.Contains(errMsg, "unknown time zone") || strings.HasPrefix(errMsg, "time: unknown format") {
		return fmt.Errorf("invalid time zone format: %w", err)
	}

	return err
}


================================================
FILE: go.mod
================================================
module github.com/go-viper/mapstructure/v2

go 1.18


================================================
FILE: internal/errors/errors.go
================================================
package errors

import "errors"

func New(text string) error {
	return errors.New(text)
}

func As(err error, target interface{}) bool {
	return errors.As(err, target)
}


================================================
FILE: internal/errors/join.go
================================================
//go:build go1.20

package errors

import "errors"

func Join(errs ...error) error {
	return errors.Join(errs...)
}


================================================
FILE: internal/errors/join_go1_19.go
================================================
//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
}


================================================
FILE: mapstructure.go
================================================
// Package mapstructure exposes functionality to convert one arbitrary
// Go type into another, typically to convert a map[string]any
// 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]any{
//	    "person": map[string]any{"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]any{
//	    "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]any{
//	    "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]any" or "map[any]any".
// See example below:
//
//	type Friend struct {
//	    Name  string
//	    Other map[string]any `mapstructure:",remain"`
//	}
//
// Given the input below, Other would be populated with the other
// values that weren't used (everything but "name"):
//
//	map[string]any{
//	    "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, or a zero-length element. 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. And likewise for the URLs field, if the
// slice is nil or empty, it won't be encoded into the destination type.
//
//	type Source struct {
//	    Age  int      `mapstructure:",omitempty"`
//	    URLs []string `mapstructure:",omitempty"`
//	}
//
// # Omit Zero Values
//
// When decoding from a struct to any other value, you may use the
// ",omitzero" 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. And likewise for the URLs field, if the
// slice is nil, it won't be encoded into the destination type.
//
// Note that if the field is a slice, and it is empty but not nil, it will
// still be encoded into the destination type.
//
//	type Source struct {
//	    Age  int      `mapstructure:",omitzero"`
//	    URLs []string `mapstructure:",omitzero"`
//	}
//
// # 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]any{
//	    "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!"
//	}
//
// # Custom Decoding with Unmarshaler
//
// Types can implement the Unmarshaler interface to control their own decoding. The interface
// behaves similarly to how UnmarshalJSON does in the standard library. It can be used as an
// alternative or companion to a DecodeHook.
//
//	type TrimmedString string
//
//	func (t *TrimmedString) UnmarshalMapstructure(input any) error {
//	    str, ok := input.(string)
//	    if !ok {
//	        return fmt.Errorf("expected string, got %T", input)
//	    }
//	    *t = TrimmedString(strings.TrimSpace(str))
//	    return nil
//	}
//
// See the Unmarshaler interface documentation for more details.
//
// # 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 any

// DecodeHookFuncType is a DecodeHookFunc which has complete information about
// the source and target types.
type DecodeHookFuncType func(reflect.Type, reflect.Type, any) (any, error)

// DecodeHookFuncKind is a DecodeHookFunc which knows only the Kinds of the
// source and target types.
type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, any) (any, 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) (any, error)

// Unmarshaler is the interface implemented by types that can unmarshal
// themselves. UnmarshalMapstructure receives the input data (potentially
// transformed by DecodeHook) and should populate the receiver with the
// decoded values.
//
// The Unmarshaler interface takes precedence over the default decoding
// logic for any type (structs, slices, maps, primitives, etc.).
type Unmarshaler interface {
	UnmarshalMapstructure(any) 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

	// AllowUnsetPointer, if set to true, will prevent fields with pointer types
	// from being reported as unset, even if ErrorUnset is true and the field was
	// not present in the input data. This allows pointer fields to be optional
	// without triggering an error when they are missing.
	AllowUnsetPointer 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

	// Deep will map structures in slices instead of copying them
	//
	//  type Parent struct {
	//      Children []Child `mapstructure:",deep"`
	//  }
	Deep 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 any

	// The tag name that mapstructure reads for field names. This
	// defaults to "mapstructure". Multiple tag names can be specified
	// as a comma-separated list (e.g., "yaml,json"), and the first
	// matching non-empty tag will be used.
	TagName string

	// RootName specifies the name to use for the root element in error messages. For example:
	//   '<rootName>' has unset fields: <fieldName>
	RootName 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 is used as a fallback comparison when the direct key lookup fails.
	// See also MapFieldName for transforming field names before lookup.
	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

	// MapFieldName is the function used to convert the struct field name to the map's key name.
	//
	// This is useful for automatically converting between naming conventions without
	// explicitly tagging each field. For example, to convert Go's PascalCase field names
	// to snake_case map keys:
	//
	//	MapFieldName: func(s string) string {
	//	    return strcase.ToSnake(s)
	//	}
	//
	// When decoding from a map to a struct, the transformed field name is used for
	// the initial lookup. If not found, MatchName is used as a fallback comparison.
	// Explicit struct tags always take precedence over MapFieldName.
	MapFieldName func(string) string

	// DisableUnmarshaler, if set to true, disables the use of the Unmarshaler
	// interface. Types implementing Unmarshaler will be decoded using the
	// standard struct decoding logic instead.
	DisableUnmarshaler 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) (any, 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 any, output any) 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 any) 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 any, output any, 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 any, output any, 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
	}

	if config.MapFieldName == nil {
		config.MapFieldName = func(s string) string {
			return s
		}
	}

	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 any) error {
	err := d.decode(d.config.RootName, 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 any) 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 any, 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]any
			inputVal = reflect.ValueOf(mapVal) // create nil map pointer
		case reflect.Slice, reflect.Array:
			var sliceVal []any
			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 newDecodeError(name, err)
		}
	}
	if isNil(input) {
		return nil
	}

	var err error
	addMetaKey := true

	// Check if the target implements Unmarshaler and use it if not disabled
	unmarshaled := false
	if !d.config.DisableUnmarshaler {
		if unmarshaler, ok := getUnmarshaler(outVal); ok {
			if err = unmarshaler.UnmarshalMapstructure(input); err != nil {
				err = newDecodeError(name, err)
			}
			unmarshaled = true
		}
	}

	if !unmarshaled {
		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 newDecodeError(name, fmt.Errorf("unsupported type: %s", 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 any, 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 newDecodeError(name, &UnconvertibleTypeError{
			Expected: val,
			Value:    data,
		})
	}

	val.Set(dataVal)
	return nil
}

func (d *Decoder) decodeString(name string, data any, 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())
				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 newDecodeError(name, &UnconvertibleTypeError{
			Expected: val,
			Value:    data,
		})
	}

	return nil
}

func (d *Decoder) decodeInt(name string, data any, 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 newDecodeError(name, &ParseError{
				Expected: val,
				Value:    data,
				Err:      wrapStrconvNumError(err),
			})
		}
	case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
		jn := data.(json.Number)
		i, err := jn.Int64()
		if err != nil {
			return newDecodeError(name, &ParseError{
				Expected: val,
				Value:    data,
				Err:      err,
			})
		}
		val.SetInt(i)
	default:
		return newDecodeError(name, &UnconvertibleTypeError{
			Expected: val,
			Value:    data,
		})
	}

	return nil
}

func (d *Decoder) decodeUint(name string, data any, 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 newDecodeError(name, &ParseError{
				Expected: val,
				Value:    data,
				Err:      fmt.Errorf("%d overflows uint", 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 newDecodeError(name, &ParseError{
				Expected: val,
				Value:    data,
				Err:      fmt.Errorf("%f overflows uint", 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 newDecodeError(name, &ParseError{
				Expected: val,
				Value:    data,
				Err:      wrapStrconvNumError(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 newDecodeError(name, &ParseError{
				Expected: val,
				Value:    data,
				Err:      wrapStrconvNumError(err),
			})
		}
		val.SetUint(i)
	default:
		return newDecodeError(name, &UnconvertibleTypeError{
			Expected: val,
			Value:    data,
		})
	}

	return nil
}

func (d *Decoder) decodeBool(name string, data any, 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 newDecodeError(name, &ParseError{
				Expected: val,
				Value:    data,
				Err:      wrapStrconvNumError(err),
			})
		}
	default:
		return newDecodeError(name, &UnconvertibleTypeError{
			Expected: val,
			Value:    data,
		})
	}

	return nil
}

func (d *Decoder) decodeFloat(name string, data any, 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 newDecodeError(name, &ParseError{
				Expected: val,
				Value:    data,
				Err:      wrapStrconvNumError(err),
			})
		}
	case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
		jn := data.(json.Number)
		i, err := jn.Float64()
		if err != nil {
			return newDecodeError(name, &ParseError{
				Expected: val,
				Value:    data,
				Err:      err,
			})
		}
		val.SetFloat(i)
	default:
		return newDecodeError(name, &UnconvertibleTypeError{
			Expected: val,
			Value:    data,
		})
	}

	return nil
}

func (d *Decoder) decodeComplex(name string, data any, val reflect.Value) error {
	dataVal := reflect.Indirect(reflect.ValueOf(data))
	dataKind := getKind(dataVal)

	switch {
	case dataKind == reflect.Complex64:
		val.SetComplex(dataVal.Complex())
	default:
		return newDecodeError(name, &UnconvertibleTypeError{
			Expected: val,
			Value:    data,
		})
	}

	return nil
}

func (d *Decoder) decodeMap(name string, data any, 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 newDecodeError(name, &UnconvertibleTypeError{
			Expected: val,
			Value:    data,
		})
	}
}

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 newDecodeError(
				name+"."+f.Name,
				fmt.Errorf("cannot assign type %q to map value field of type %q", v.Type(), valMap.Type().Elem()),
			)
		}

		tagValue, _ := getTagValue(f, d.config.TagName)
		keyName := d.config.MapFieldName(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

		// If Deep is set in the config, set as default value.
		deep := d.config.Deep

		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.Contains(tagValue[index+1:], "omitempty") && isEmptyValue(v) {
				continue
			}

			// If "omitzero" is specified in the tag, it ignores zero values.
			if strings.Contains(tagValue[index+1:], "omitzero") && v.IsZero() {
				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 newDecodeError(
						name+"."+f.Name,
						fmt.Errorf("cannot squash non-struct type %q", v.Type()),
					)
				}
			} else {
				if strings.Contains(tagValue[index+1:], "remain") {
					if v.Kind() != reflect.Map {
						return newDecodeError(
							name+"."+f.Name,
							fmt.Errorf("error remain-tag field with invalid type: %q", v.Type()),
						)
					}

					ptr := v.MapRange()
					for ptr.Next() {
						valMap.SetMapIndex(ptr.Key(), ptr.Value())
					}
					continue
				}
			}

			deep = deep || strings.Contains(tagValue[index+1:], "deep")

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

		case reflect.Slice:
			if deep {
				var childType reflect.Type
				switch v.Type().Elem().Kind() {
				case reflect.Struct:
					childType = reflect.TypeOf(map[string]any{})
				default:
					childType = v.Type().Elem()
				}

				sType := reflect.SliceOf(childType)

				addrVal := reflect.New(sType)

				vSlice := reflect.MakeSlice(sType, v.Len(), v.Cap())

				if v.Len() > 0 {
					reflect.Indirect(addrVal).Set(vSlice)

					err := d.decode(keyName, v.Interface(), reflect.Indirect(addrVal))
					if err != nil {
						return err
					}
				}

				vSlice = reflect.Indirect(addrVal)

				valMap.SetMapIndex(reflect.ValueOf(keyName), vSlice)

				break
			}

			// When deep mapping is not needed, fallthrough to normal copy
			fallthrough

		default:
			valMap.SetMapIndex(reflect.ValueOf(keyName), v)
		}
	}

	if val.CanAddr() {
		val.Set(valMap)
	}

	return nil
}

func (d *Decoder) decodePtr(name string, data any, 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 any, 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 newDecodeError(name, &UnconvertibleTypeError{
			Expected: val,
			Value:    data,
		})
	}
	val.Set(dataVal)
	return nil
}

func (d *Decoder) decodeSlice(name string, data any, 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, []any{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, []any{data}, val)
			}
		}

		return newDecodeError(name,
			fmt.Errorf("source data must be an array or slice, got %s", 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 any, 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, []any{data}, val)
				}
			}

			return newDecodeError(name,
				fmt.Errorf("source data must be an array or slice, got %s", dataValKind))

		}
		if dataVal.Len() > arrayType.Len() {
			return newDecodeError(name,
				fmt.Errorf("expected source data to have length less or equal to %d, got %d", 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 any, 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]any)(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 newDecodeError(name,
			fmt.Errorf("expected a map or struct, got %q", dataValKind))
	}
}

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 newDecodeError(name,
			fmt.Errorf("needs a map with string keys, has %q keys", kind))
	}

	dataValKeys := make(map[reflect.Value]struct{})
	dataValKeysUnused := make(map[any]struct{})
	for _, dataValKey := range dataVal.MapKeys() {
		dataValKeys[dataValKey] = struct{}{}
		dataValKeysUnused[dataValKey.Interface()] = struct{}{}
	}

	targetValKeysUnused := make(map[any]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 := getTagParts(fieldType, d.config.TagName)
			if len(tagParts) == 0 {
				tagParts = []string{""}
			}
			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())
					}
				case reflect.Ptr:
					if fieldVal.Type().Elem().Kind() == reflect.Struct {
						if fieldVal.IsNil() {
							fieldVal.Set(reflect.New(fieldVal.Type().Elem()))
						}
						structs = append(structs, fieldVal.Elem())
					} else {
						errs = append(errs, newDecodeError(
							name+"."+fieldType.Name,
							fmt.Errorf("unsupported type for squashed pointer: %s", fieldVal.Type().Elem().Kind()),
						))
					}
				default:
					errs = append(errs, newDecodeError(
						name+"."+fieldType.Name,
						fmt.Errorf("unsupported type for squash: %s", 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, _ := getTagValue(field, d.config.TagName)
		if tagValue == "" && d.config.IgnoreUntaggedFields {
			continue
		}
		tagValue = strings.SplitN(tagValue, ",", 2)[0]
		if tagValue != "" {
			fieldName = tagValue
		} else {
			fieldName = d.config.MapFieldName(fieldName)
		}

		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.
				if !(d.config.AllowUnsetPointer && fieldValue.Kind() == reflect.Ptr) {
					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[any]any{}
		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)

		// Improve error message when name is empty by showing the target struct type
		// in the case where it is empty for embedded structs.
		errorName := name
		if errorName == "" {
			errorName = val.Type().String()
		}
		errs = append(errs, newDecodeError(
			errorName,
			fmt.Errorf("has invalid keys: %s", strings.Join(keys, ", ")),
		))
	}

	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)

		errs = append(errs, newDecodeError(
			name,
			fmt.Errorf("has unset fields: %s", strings.Join(keys, ", ")),
		))
	}

	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 && hasAnyTag(f, tagName) { // check for mapstructure tags inside
			return true
		}
	}
	return false
}

func dereferencePtrToStructIfNeeded(v reflect.Value, tagName string) reflect.Value {
	if v.Kind() != reflect.Ptr {
		return v
	}

	switch v.Elem().Kind() {
	case reflect.Slice:
		return v.Elem()

	case reflect.Struct:
		deref := v.Elem()
		derefT := deref.Type()
		if isStructTypeConvertibleToMap(derefT, true, tagName) {
			return deref
		}
		return v

	default:
		return v
	}
}

func hasAnyTag(field reflect.StructField, tagName string) bool {
	_, ok := getTagValue(field, tagName)
	return ok
}

func getTagParts(field reflect.StructField, tagName string) []string {
	tagValue, ok := getTagValue(field, tagName)
	if !ok {
		return nil
	}
	return strings.Split(tagValue, ",")
}

func getTagValue(field reflect.StructField, tagName string) (string, bool) {
	for _, name := range splitTagNames(tagName) {
		if tag := field.Tag.Get(name); tag != "" {
			return tag, true
		}
	}
	return "", false
}

func splitTagNames(tagName string) []string {
	if tagName == "" {
		return []string{"mapstructure"}
	}
	parts := strings.Split(tagName, ",")
	result := make([]string, 0, len(parts))

	for _, name := range parts {
		name = strings.TrimSpace(name)
		if name != "" {
			result = append(result, name)
		}
	}

	return result
}

// unmarshalerType is cached for performance
var unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()

// getUnmarshaler checks if the value implements Unmarshaler and returns
// the Unmarshaler and a boolean indicating if it was found. It handles both
// pointer and value receivers.
func getUnmarshaler(val reflect.Value) (Unmarshaler, bool) {
	// Skip invalid or nil values
	if !val.IsValid() {
		return nil, false
	}

	switch val.Kind() {
	case reflect.Pointer, reflect.Interface:
		if val.IsNil() {
			return nil, false
		}
	}

	// Check pointer receiver first (most common case)
	if val.CanAddr() {
		ptrVal := val.Addr()
		// Quick check: if no methods, can't implement any interface
		if ptrVal.Type().NumMethod() > 0 && ptrVal.Type().Implements(unmarshalerType) {
			return ptrVal.Interface().(Unmarshaler), true
		}
	}

	// Check value receiver
	// Quick check: if no methods, can't implement any interface
	if val.Type().NumMethod() > 0 && val.CanInterface() && val.Type().Implements(unmarshalerType) {
		return val.Interface().(Unmarshaler), true
	}

	return nil, false
}


================================================
FILE: mapstructure_benchmark_test.go
================================================
package 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]any{
		"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 any, v any) 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]any{
		"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]any{
		"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]any{
		"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]any{
		"vstring": "foo",
		"Basic": map[string]any{
			"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]any{
		"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":    []any{},
		"MapToSlice":    map[string]any{},
	}

	var resultStrict TypeConversionResult
	for i := 0; i < b.N; i++ {
		Decode(input, &resultStrict)
	}
}

func Benchmark_DecodeMap(b *testing.B) {
	input := map[string]any{
		"vfoo": "foo",
		"vother": map[any]any{
			"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]any{
		"value": map[string]any{
			"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]any{
		"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]any{
		"value": []map[string]any{
			{"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]any{
		"name":   123,              // number => string
		"age":    "42",             // string => number
		"emails": map[string]any{}, // 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]any{
		"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]any{
		"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]any{
		"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]any `mapstructure:",remain"`
	}

	input := map[string]any{
		"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]any)
		_ = Decode(&person, &result)
	}
}


================================================
FILE: mapstructure_bugs_test.go
================================================
package mapstructure

import (
	"reflect"
	"testing"
	"time"
)

// GH-1, GH-10, GH-96
func TestDecode_NilValue(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name       string
		in         any
		target     any
		out        any
		metaKeys   []string
		metaUnused []string
	}{
		{
			"all nil",
			&map[string]any{
				"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]any{
				"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]any{
				"vother": nil,
			},
			&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
			&Map{Vfoo: "foo", Vother: nil},
			[]string{"Vother"},
			[]string{},
		},
		{
			"unused values",
			&map[string]any{
				"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[any]any{
				"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[any]any{
				"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[any]any{
				"vother": nil,
			},
			&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
			&Map{Vfoo: "foo", Vother: nil},
			[]string{"Vother"},
			[]string{},
		},
		{
			"map interface unused values",
			&map[any]any{
				"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]any{
		"vfoo": "foo",
		"vbar": map[string]any{
			"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]any{
		"vfoo": "foo",
		"vbars": []map[string]any{
			{"vstring": "foo", "vint": 42, "vbool": true},
			{"vint": 42, "vbool": true},
		},
		"vempty": []map[string]any{
			{"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]any{
		"vfoo": "foo",
		"vbar": map[string]any{
			"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 any, rawVal any) 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]any{
			"vfoo": []string{"1"},
		}

		result := &TestStruct{}

		err := decode(input, &result)
		if err != nil {
			t.Fatalf("got an err: %s", err.Error())
		}
	}

	{
		input := map[string]any{
			"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]any{
			"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[any]any{"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 any) (any, 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 any) (any, 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]any{
		"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]any)(nil))

	var actual TsWrapper
	decoder, err := NewDecoder(&DecoderConfig{
		Result: &actual,
		DecodeHook: func(from, to reflect.Type, data any) (any, error) {
			if from == timePtrType && to == mapStrInfType {
				ts := data.(*time.Time)
				nanos := ts.UnixNano()

				seconds := nanos / 1000000000
				nanos = nanos % 1000000000

				return &map[string]any{
					"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]any{
		"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]any

	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]any{"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]any

		if err := Decode(iii, &actual); err != nil {
			t.Fatal(err)
		}

		expected := map[string]any{
			"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]any{
			"foo": "bar",
		}

		i := &v
		ii := &i
		iii := &ii

		var actual map[string]any

		if err := Decode(iii, &actual); err != nil {
			t.Fatal(err)
		}

		expected := map[string]any{
			"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]any{
			{
				"foo": "bar",
			},
		}

		i := &v
		ii := &i
		iii := &ii

		var actual map[string]any

		if err := WeakDecode(iii, &actual); err != nil {
			t.Fatal(err)
		}

		expected := map[string]any{
			"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]any{
			{
				"foo": "bar",
			},
		}

		i := &v
		ii := &i
		iii := &ii

		var actual map[string]any

		if err := WeakDecode(iii, &actual); err != nil {
			t.Fatal(err)
		}

		expected := map[string]any{
			"foo": "bar",
		}

		if !reflect.DeepEqual(actual, expected) {
			t.Fatalf("expected: %#v, got: %#v", expected, actual)
		}
	})
}


================================================
FILE: mapstructure_examples_test.go
================================================
package 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]any{
		"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]any{
		"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'
	// 'Age' expected type 'int', got unconvertible type 'string'
	// 'Emails[0]' expected type 'string', got unconvertible type 'int'
	// 'Emails[1]' expected type 'string', got unconvertible type 'int'
	// 'Emails[2]' expected type 'string', got unconvertible type 'int'
}

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]any{
		"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]any{
		"name":   123,              // number => string
		"age":    "42",             // string => number
		"emails": map[string]any{}, // 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]any{
		"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
Download .txt
gitextract_89bd1yif/

├── .editorconfig
├── .envrc
├── .github/
│   ├── .editorconfig
│   ├── dependabot.yaml
│   └── workflows/
│       ├── analysis-scorecard.yaml
│       └── ci.yaml
├── .gitignore
├── .golangci.yaml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── decode_hooks.go
├── decode_hooks_test.go
├── devenv.nix
├── devenv.yaml
├── errors.go
├── go.mod
├── internal/
│   └── errors/
│       ├── errors.go
│       ├── join.go
│       └── join_go1_19.go
├── mapstructure.go
├── mapstructure_benchmark_test.go
├── mapstructure_bugs_test.go
├── mapstructure_examples_test.go
├── mapstructure_test.go
├── reflect_go1_19.go
└── reflect_go1_20.go
Download .txt
SYMBOL INDEX (408 symbols across 13 files)

FILE: decode_hooks.go
  function safeInterface (line 18) | func safeInterface(v reflect.Value) any {
  function typedDecodeHook (line 27) | func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
  function cachedDecodeHook (line 52) | func cachedDecodeHook(raw DecodeHookFunc) func(from reflect.Value, to re...
  function DecodeHookExec (line 82) | func DecodeHookExec(
  function ComposeDecodeHookFunc (line 109) | func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
  function OrComposeDecodeHookFunc (line 140) | func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc {
  function StringToSliceHookFunc (line 166) | func StringToSliceHookFunc(sep string) DecodeHookFunc {
  function StringToWeakSliceHookFunc (line 192) | func StringToWeakSliceHookFunc(sep string) DecodeHookFunc {
  function StringToTimeDurationHookFunc (line 213) | func StringToTimeDurationHookFunc() DecodeHookFunc {
  function StringToTimeLocationHookFunc (line 235) | func StringToTimeLocationHookFunc() DecodeHookFunc {
  function StringToURLHookFunc (line 255) | func StringToURLHookFunc() DecodeHookFunc {
  function StringToIPHookFunc (line 277) | func StringToIPHookFunc() DecodeHookFunc {
  function StringToIPNetHookFunc (line 302) | func StringToIPNetHookFunc() DecodeHookFunc {
  function StringToTimeHookFunc (line 323) | func StringToTimeHookFunc(layout string) DecodeHookFunc {
  function WeaklyTypedHook (line 348) | func WeaklyTypedHook(
  function RecursiveStructToMapHookFunc (line 380) | func RecursiveStructToMapHookFunc() DecodeHookFunc {
  function TextUnmarshallerHookFunc (line 401) | func TextUnmarshallerHookFunc() DecodeHookFuncType {
  function StringToNetIPAddrHookFunc (line 428) | func StringToNetIPAddrHookFunc() DecodeHookFunc {
  function StringToNetIPAddrPortHookFunc (line 450) | func StringToNetIPAddrPortHookFunc() DecodeHookFunc {
  function StringToNetIPPrefixHookFunc (line 472) | func StringToNetIPPrefixHookFunc() DecodeHookFunc {
  function StringToBasicTypeHookFunc (line 495) | func StringToBasicTypeHookFunc() DecodeHookFunc {
  function StringToInt8HookFunc (line 520) | func StringToInt8HookFunc() DecodeHookFunc {
  function StringToUint8HookFunc (line 534) | func StringToUint8HookFunc() DecodeHookFunc {
  function StringToInt16HookFunc (line 548) | func StringToInt16HookFunc() DecodeHookFunc {
  function StringToUint16HookFunc (line 562) | func StringToUint16HookFunc() DecodeHookFunc {
  function StringToInt32HookFunc (line 576) | func StringToInt32HookFunc() DecodeHookFunc {
  function StringToUint32HookFunc (line 590) | func StringToUint32HookFunc() DecodeHookFunc {
  function StringToInt64HookFunc (line 604) | func StringToInt64HookFunc() DecodeHookFunc {
  function StringToUint64HookFunc (line 618) | func StringToUint64HookFunc() DecodeHookFunc {
  function StringToIntHookFunc (line 632) | func StringToIntHookFunc() DecodeHookFunc {
  function StringToUintHookFunc (line 646) | func StringToUintHookFunc() DecodeHookFunc {
  function StringToFloat32HookFunc (line 660) | func StringToFloat32HookFunc() DecodeHookFunc {
  function StringToFloat64HookFunc (line 674) | func StringToFloat64HookFunc() DecodeHookFunc {
  function StringToBoolHookFunc (line 688) | func StringToBoolHookFunc() DecodeHookFunc {
  function StringToByteHookFunc (line 702) | func StringToByteHookFunc() DecodeHookFunc {
  function StringToRuneHookFunc (line 708) | func StringToRuneHookFunc() DecodeHookFunc {
  function StringToComplex64HookFunc (line 714) | func StringToComplex64HookFunc() DecodeHookFunc {
  function StringToComplex128HookFunc (line 728) | func StringToComplex128HookFunc() DecodeHookFunc {

FILE: decode_hooks_test.go
  type decodeHookTestSuite (line 18) | type decodeHookTestSuite struct
  method Run (line 24) | func (ts decodeHookTestSuite[F, T]) Run(t *testing.T) {
  type decodeHookTestCase (line 69) | type decodeHookTestCase struct
  method Run (line 74) | func (tc decodeHookTestCase[F, T]) Run(t *testing.T, fn DecodeHookFunc) {
  type decodeHookFailureTestCase (line 87) | type decodeHookFailureTestCase struct
  method Run (line 91) | func (tc decodeHookFailureTestCase[F, T]) Run(t *testing.T, fn DecodeHoo...
  function TestComposeDecodeHookFunc (line 100) | func TestComposeDecodeHookFunc(t *testing.T) {
  function TestComposeDecodeHookFunc_err (line 129) | func TestComposeDecodeHookFunc_err(t *testing.T) {
  function TestComposeDecodeHookFunc_kinds (line 147) | func TestComposeDecodeHookFunc_kinds(t *testing.T) {
  function TestOrComposeDecodeHookFunc (line 179) | func TestOrComposeDecodeHookFunc(t *testing.T) {
  function TestOrComposeDecodeHookFunc_correctValueIsLast (line 208) | func TestOrComposeDecodeHookFunc_correctValueIsLast(t *testing.T) {
  function TestOrComposeDecodeHookFunc_err (line 245) | func TestOrComposeDecodeHookFunc_err(t *testing.T) {
  function TestComposeDecodeHookFunc_safe_nofuncs (line 274) | func TestComposeDecodeHookFunc_safe_nofuncs(t *testing.T) {
  function TestComposeDecodeHookFunc_ReflectValueHook (line 306) | func TestComposeDecodeHookFunc_ReflectValueHook(t *testing.T) {
  function TestComposeDecodeHookFunc_NilValue (line 338) | func TestComposeDecodeHookFunc_NilValue(t *testing.T) {
  function TestComposeDecodeHookFunc_DecodeNilRemain (line 357) | func TestComposeDecodeHookFunc_DecodeNilRemain(t *testing.T) {
  function TestStringToSliceHookFunc (line 385) | func TestStringToSliceHookFunc(t *testing.T) {
  function TestStringToWeakSliceHookFunc (line 500) | func TestStringToWeakSliceHookFunc(t *testing.T) {
  function TestStringToTimeDurationHookFunc (line 557) | func TestStringToTimeDurationHookFunc(t *testing.T) {
  function TestStringToTimeLocationHookFunc (line 656) | func TestStringToTimeLocationHookFunc(t *testing.T) {
  function TestStringToURLHookFunc (line 685) | func TestStringToURLHookFunc(t *testing.T) {
  function TestStringToTimeHookFunc (line 721) | func TestStringToTimeHookFunc(t *testing.T) {
  function TestStringToIPHookFunc (line 752) | func TestStringToIPHookFunc(t *testing.T) {
  function TestStringToIPNetHookFunc (line 791) | func TestStringToIPNetHookFunc(t *testing.T) {
  function TestWeaklyTypedHook (line 826) | func TestWeaklyTypedHook(t *testing.T) {
  function TestStructToMapHookFuncTabled (line 892) | func TestStructToMapHookFuncTabled(t *testing.T) {
  function TestTextUnmarshallerHookFunc (line 1005) | func TestTextUnmarshallerHookFunc(t *testing.T) {
  function TestStringToNetIPAddrHookFunc (line 1033) | func TestStringToNetIPAddrHookFunc(t *testing.T) {
  function TestStringToNetIPAddrPortHookFunc (line 1071) | func TestStringToNetIPAddrPortHookFunc(t *testing.T) {
  function TestStringToNetIPPrefixHookFunc (line 1110) | func TestStringToNetIPPrefixHookFunc(t *testing.T) {
  function TestStringToBasicTypeHookFunc (line 1151) | func TestStringToBasicTypeHookFunc(t *testing.T) {
  function TestStringToInt8HookFunc (line 1193) | func TestStringToInt8HookFunc(t *testing.T) {
  function TestStringToUint8HookFunc (line 1255) | func TestStringToUint8HookFunc(t *testing.T) {
  function TestStringToInt16HookFunc (line 1308) | func TestStringToInt16HookFunc(t *testing.T) {
  function TestStringToUint16HookFunc (line 1331) | func TestStringToUint16HookFunc(t *testing.T) {
  function TestStringToInt32HookFunc (line 1354) | func TestStringToInt32HookFunc(t *testing.T) {
  function TestStringToUint32HookFunc (line 1377) | func TestStringToUint32HookFunc(t *testing.T) {
  function TestStringToInt64HookFunc (line 1400) | func TestStringToInt64HookFunc(t *testing.T) {
  function TestStringToUint64HookFunc (line 1423) | func TestStringToUint64HookFunc(t *testing.T) {
  function TestStringToIntHookFunc (line 1446) | func TestStringToIntHookFunc(t *testing.T) {
  function TestStringToUintHookFunc (line 1469) | func TestStringToUintHookFunc(t *testing.T) {
  function TestStringToFloat32HookFunc (line 1492) | func TestStringToFloat32HookFunc(t *testing.T) {
  function TestStringToFloat64HookFunc (line 1586) | func TestStringToFloat64HookFunc(t *testing.T) {
  function TestStringToComplex64HookFunc (line 1675) | func TestStringToComplex64HookFunc(t *testing.T) {
  function TestStringToBoolHookFunc (line 1846) | func TestStringToBoolHookFunc(t *testing.T) {
  function TestStringToComplex128HookFunc (line 1928) | func TestStringToComplex128HookFunc(t *testing.T) {
  function TestErrorLeakageDecodeHook (line 2104) | func TestErrorLeakageDecodeHook(t *testing.T) {

FILE: errors.go
  type Error (line 17) | type Error interface
  type DecodeError (line 25) | type DecodeError struct
    method Name (line 37) | func (e *DecodeError) Name() string {
    method Unwrap (line 41) | func (e *DecodeError) Unwrap() error {
    method Error (line 45) | func (e *DecodeError) Error() string {
    method mapstructure (line 49) | func (*DecodeError) mapstructure() {}
  function newDecodeError (line 30) | func newDecodeError(name string, err error) *DecodeError {
  type ParseError (line 53) | type ParseError struct
    method Error (line 59) | func (e *ParseError) Error() string {
    method mapstructure (line 63) | func (*ParseError) mapstructure() {}
  type UnconvertibleTypeError (line 67) | type UnconvertibleTypeError struct
    method Error (line 72) | func (e *UnconvertibleTypeError) Error() string {
    method mapstructure (line 80) | func (*UnconvertibleTypeError) mapstructure() {}
  function wrapStrconvNumError (line 82) | func wrapStrconvNumError(err error) error {
  type strconvNumError (line 94) | type strconvNumError struct
    method Error (line 98) | func (e *strconvNumError) Error() string {
    method Unwrap (line 102) | func (e *strconvNumError) Unwrap() error { return e.Err }
  function wrapUrlError (line 104) | func wrapUrlError(err error) error {
  type urlError (line 116) | type urlError struct
    method Error (line 120) | func (e *urlError) Error() string {
    method Unwrap (line 124) | func (e *urlError) Unwrap() error { return e.Err }
  function wrapNetParseError (line 126) | func wrapNetParseError(err error) error {
  type netParseError (line 138) | type netParseError struct
    method Error (line 142) | func (e *netParseError) Error() string {
    method Unwrap (line 146) | func (e *netParseError) Unwrap() error { return e.Err }
  function wrapTimeParseError (line 148) | func wrapTimeParseError(err error) error {
  type timeParseError (line 160) | type timeParseError struct
    method Error (line 164) | func (e *timeParseError) Error() string {
    method Unwrap (line 172) | func (e *timeParseError) Unwrap() error { return e.Err }
  function wrapNetIPParseAddrError (line 174) | func wrapNetIPParseAddrError(err error) error {
  function wrapNetIPParseAddrPortError (line 188) | func wrapNetIPParseAddrPortError(err error) error {
  function wrapNetIPParsePrefixError (line 203) | func wrapNetIPParsePrefixError(err error) error {
  function wrapTimeParseDurationError (line 217) | func wrapTimeParseDurationError(err error) error {
  function wrapTimeParseLocationError (line 234) | func wrapTimeParseLocationError(err error) error {

FILE: internal/errors/errors.go
  function New (line 5) | func New(text string) error {
  function As (line 9) | func As(err error, target interface{}) bool {

FILE: internal/errors/join.go
  function Join (line 7) | func Join(errs ...error) error {

FILE: internal/errors/join_go1_19.go
  function Join (line 17) | func Join(errs ...error) error {
  type joinError (line 38) | type joinError struct
    method Error (line 42) | func (e *joinError) Error() string {
    method Unwrap (line 59) | func (e *joinError) Unwrap() []error {

FILE: mapstructure.go
  type DecodeHookFunc (line 226) | type DecodeHookFunc
  type DecodeHookFuncType (line 230) | type DecodeHookFuncType
  type DecodeHookFuncKind (line 234) | type DecodeHookFuncKind
  type DecodeHookFuncValue (line 238) | type DecodeHookFuncValue
  type Unmarshaler (line 247) | type Unmarshaler interface
  type DecoderConfig (line 253) | type DecoderConfig struct
  type Decoder (line 386) | type Decoder struct
    method Decode (line 532) | func (d *Decoder) Decode(input any) error {
    method decode (line 554) | func (d *Decoder) decode(name string, input any, outVal reflect.Value)...
    method decodeBasic (line 672) | func (d *Decoder) decodeBasic(name string, data any, val reflect.Value...
    method decodeString (line 729) | func (d *Decoder) decodeString(name string, data any, val reflect.Valu...
    method decodeInt (line 782) | func (d *Decoder) decodeInt(name string, data any, val reflect.Value) ...
    method decodeUint (line 837) | func (d *Decoder) decodeUint(name string, data any, val reflect.Value)...
    method decodeBool (line 908) | func (d *Decoder) decodeBool(name string, data any, val reflect.Value)...
    method decodeFloat (line 944) | func (d *Decoder) decodeFloat(name string, data any, val reflect.Value...
    method decodeComplex (line 999) | func (d *Decoder) decodeComplex(name string, data any, val reflect.Val...
    method decodeMap (line 1016) | func (d *Decoder) decodeMap(name string, data any, val reflect.Value) ...
    method decodeMapFromSlice (line 1061) | func (d *Decoder) decodeMapFromSlice(name string, dataVal reflect.Valu...
    method decodeMapFromMap (line 1080) | func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value,...
    method decodeMapFromStruct (line 1129) | func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Val...
    method decodePtr (line 1306) | func (d *Decoder) decodePtr(name string, data any, val reflect.Value) ...
    method decodeFunc (line 1353) | func (d *Decoder) decodeFunc(name string, data any, val reflect.Value)...
    method decodeSlice (line 1367) | func (d *Decoder) decodeSlice(name string, data any, val reflect.Value...
    method decodeArray (line 1441) | func (d *Decoder) decodeArray(name string, data any, val reflect.Value...
    method decodeStruct (line 1502) | func (d *Decoder) decodeStruct(name string, data any, val reflect.Valu...
    method decodeStructFromMap (line 1546) | func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflec...
  type Metadata (line 393) | type Metadata struct
  function Decode (line 409) | func Decode(input any, output any) error {
  function WeakDecode (line 425) | func WeakDecode(input, output any) error {
  function DecodeMetadata (line 442) | func DecodeMetadata(input any, output any, metadata *Metadata) error {
  function WeakDecodeMetadata (line 459) | func WeakDecodeMetadata(input any, output any, metadata *Metadata) error {
  function NewDecoder (line 477) | func NewDecoder(config *DecoderConfig) (*Decoder, error) {
  function isNil (line 545) | func isNil(input any) bool {
  function isEmptyValue (line 1804) | func isEmptyValue(v reflect.Value) bool {
  function getKind (line 1822) | func getKind(val reflect.Value) reflect.Kind {
  function isStructTypeConvertibleToMap (line 1839) | func isStructTypeConvertibleToMap(typ reflect.Type, checkMapstructureTag...
  function dereferencePtrToStructIfNeeded (line 1852) | func dereferencePtrToStructIfNeeded(v reflect.Value, tagName string) ref...
  function hasAnyTag (line 1874) | func hasAnyTag(field reflect.StructField, tagName string) bool {
  function getTagParts (line 1879) | func getTagParts(field reflect.StructField, tagName string) []string {
  function getTagValue (line 1887) | func getTagValue(field reflect.StructField, tagName string) (string, boo...
  function splitTagNames (line 1896) | func splitTagNames(tagName string) []string {
  function getUnmarshaler (line 1919) | func getUnmarshaler(val reflect.Value) (Unmarshaler, bool) {

FILE: mapstructure_benchmark_test.go
  type Person (line 8) | type Person struct
  function Benchmark_Decode (line 15) | func Benchmark_Decode(b *testing.B) {
  function decodeViaJSON (line 33) | func decodeViaJSON(data any, v any) error {
  function Benchmark_DecodeViaJSON (line 43) | func Benchmark_DecodeViaJSON(b *testing.B) {
  function Benchmark_JSONUnmarshal (line 59) | func Benchmark_JSONUnmarshal(b *testing.B) {
  function Benchmark_DecodeBasic (line 80) | func Benchmark_DecodeBasic(b *testing.B) {
  function Benchmark_DecodeEmbedded (line 100) | func Benchmark_DecodeEmbedded(b *testing.B) {
  function Benchmark_DecodeTypeConversion (line 115) | func Benchmark_DecodeTypeConversion(b *testing.B) {
  function Benchmark_DecodeMap (line 147) | func Benchmark_DecodeMap(b *testing.B) {
  function Benchmark_DecodeMapOfStruct (line 162) | func Benchmark_DecodeMapOfStruct(b *testing.B) {
  function Benchmark_DecodeSlice (line 176) | func Benchmark_DecodeSlice(b *testing.B) {
  function Benchmark_DecodeSliceOfStruct (line 188) | func Benchmark_DecodeSliceOfStruct(b *testing.B) {
  function Benchmark_DecodeWeaklyTypedInput (line 202) | func Benchmark_DecodeWeaklyTypedInput(b *testing.B) {
  function Benchmark_DecodeMetadata (line 228) | func Benchmark_DecodeMetadata(b *testing.B) {
  function Benchmark_DecodeMetadataEmbedded (line 252) | func Benchmark_DecodeMetadataEmbedded(b *testing.B) {
  function Benchmark_DecodeTagged (line 275) | func Benchmark_DecodeTagged(b *testing.B) {
  function Benchmark_DecodeWithRemainingFields (line 287) | func Benchmark_DecodeWithRemainingFields(b *testing.B) {

FILE: mapstructure_bugs_test.go
  function TestDecode_NilValue (line 10) | func TestDecode_NilValue(t *testing.T) {
  function TestNestedTypePointerWithDefaults (line 145) | func TestNestedTypePointerWithDefaults(t *testing.T) {
  type NestedSlice (line 193) | type NestedSlice struct
  function TestNestedTypeSliceWithDefaults (line 200) | func TestNestedTypeSliceWithDefaults(t *testing.T) {
  function TestNestedTypeWithDefaults (line 240) | func TestNestedTypeWithDefaults(t *testing.T) {
  function TestDecodeSliceToEmptySliceWOZeroing (line 289) | func TestDecodeSliceToEmptySliceWOZeroing(t *testing.T) {
  function TestNextSquashMapstructure (line 356) | func TestNextSquashMapstructure(t *testing.T) {
  type ImplementsInterfacePointerReceiver (line 373) | type ImplementsInterfacePointerReceiver struct
    method DoStuff (line 377) | func (i *ImplementsInterfacePointerReceiver) DoStuff() {}
  type ImplementsInterfaceValueReceiver (line 379) | type ImplementsInterfaceValueReceiver
    method DoStuff (line 381) | func (i ImplementsInterfaceValueReceiver) DoStuff() {}
  function TestDecode_DecodeHookInterface (line 384) | func TestDecode_DecodeHookInterface(t *testing.T) {
  function TestDecodeBadDataTypeInSlice (line 463) | func TestDecodeBadDataTypeInSlice(t *testing.T) {
  function TestDecodeIntermediateMapsSettable (line 480) | func TestDecodeIntermediateMapsSettable(t *testing.T) {
  function TestDecode_weakEmptyStringToInt (line 541) | func TestDecode_weakEmptyStringToInt(t *testing.T) {
  function TestMapSquash (line 569) | func TestMapSquash(t *testing.T) {
  function TestMapOmitEmptyWithEmptyFieldnameInTag (line 603) | func TestMapOmitEmptyWithEmptyFieldnameInTag(t *testing.T) {
  type HasNonComparableType (line 627) | type HasNonComparableType struct
  function TestDecode_nonComparableType (line 631) | func TestDecode_nonComparableType(t *testing.T) {
  function TestDecodeToMapWithMultipleIndirection (line 643) | func TestDecodeToMapWithMultipleIndirection(t *testing.T) {

FILE: mapstructure_examples_test.go
  function ExampleDecode (line 10) | func ExampleDecode() {
  function ExampleDecode_errors (line 41) | func ExampleDecode_errors() {
  function ExampleDecode_metadata (line 75) | func ExampleDecode_metadata() {
  function ExampleDecode_weaklyTypedInput (line 114) | func ExampleDecode_weaklyTypedInput() {
  function ExampleDecode_tags (line 150) | func ExampleDecode_tags() {
  function ExampleDecode_embeddedStruct (line 174) | func ExampleDecode_embeddedStruct() {
  function ExampleDecode_remainingData (line 208) | func ExampleDecode_remainingData() {
  function ExampleDecode_remainingDataDecodeBackToMapInFlatFormat (line 234) | func ExampleDecode_remainingDataDecodeBackToMapInFlatFormat() {
  function ExampleDecode_omitempty (line 269) | func ExampleDecode_omitempty() {
  function ExampleDecode_decodeHookFunc (line 296) | func ExampleDecode_decodeHookFunc() {
  type ExampleServerConfig (line 363) | type ExampleServerConfig struct
    method UnmarshalMapstructure (line 370) | func (s *ExampleServerConfig) UnmarshalMapstructure(data any) error {
  function ExampleDecode_unmarshaler (line 391) | func ExampleDecode_unmarshaler() {

FILE: mapstructure_test.go
  type Basic (line 15) | type Basic struct
  type BasicPointer (line 37) | type BasicPointer struct
  type BasicSquash (line 51) | type BasicSquash struct
  type BasicJSONInline (line 55) | type BasicJSONInline struct
  type Embedded (line 59) | type Embedded struct
  type EmbeddedPointer (line 64) | type EmbeddedPointer struct
  type EmbeddedSquash (line 69) | type EmbeddedSquash struct
  type EmbeddedPointerSquash (line 74) | type EmbeddedPointerSquash struct
  type BasicMapStructure (line 79) | type BasicMapStructure struct
  type NestedPointerWithMapstructure (line 84) | type NestedPointerWithMapstructure struct
  type EmbeddedPointerSquashWithNestedMapstructure (line 88) | type EmbeddedPointerSquashWithNestedMapstructure struct
  type EmbeddedAndNamed (line 93) | type EmbeddedAndNamed struct
  type SliceAlias (line 99) | type SliceAlias
  type EmbeddedSlice (line 101) | type EmbeddedSlice struct
  type ArrayAlias (line 106) | type ArrayAlias
  type EmbeddedArray (line 108) | type EmbeddedArray struct
  type SquashOnNonStructType (line 113) | type SquashOnNonStructType struct
  type TestInterface (line 117) | type TestInterface interface
  type TestInterfaceImpl (line 123) | type TestInterfaceImpl struct
    method GetVfoo (line 127) | func (t *TestInterfaceImpl) GetVfoo() string {
    method GetVbarfoo (line 131) | func (t *TestInterfaceImpl) GetVbarfoo() string {
    method GetVfoobar (line 135) | func (t *TestInterfaceImpl) GetVfoobar() string {
  type TestNestedInterfaceImpl (line 139) | type TestNestedInterfaceImpl struct
    method GetVfoo (line 144) | func (t *TestNestedInterfaceImpl) GetVfoo() string {
    method GetVbarfoo (line 148) | func (t *TestNestedInterfaceImpl) GetVbarfoo() string {
    method GetVfoobar (line 152) | func (t *TestNestedInterfaceImpl) GetVfoobar() string {
  type SquashOnInterfaceType (line 156) | type SquashOnInterfaceType struct
  type NestedSquash (line 161) | type NestedSquash struct
  type SquashOnNestedInterfaceType (line 166) | type SquashOnNestedInterfaceType struct
  type Map (line 171) | type Map struct
  type MapOfStruct (line 176) | type MapOfStruct struct
  type Nested (line 180) | type Nested struct
  type NestedPointer (line 185) | type NestedPointer struct
  type NilInterface (line 190) | type NilInterface struct
  type NilPointer (line 194) | type NilPointer struct
  type Slice (line 198) | type Slice struct
  type SliceOfByte (line 203) | type SliceOfByte struct
  type SliceOfAlias (line 208) | type SliceOfAlias struct
  type SliceOfStruct (line 213) | type SliceOfStruct struct
  type SlicePointer (line 217) | type SlicePointer struct
  type Array (line 221) | type Array struct
  type ArrayOfStruct (line 226) | type ArrayOfStruct struct
  type Func (line 230) | type Func struct
  type Tagged (line 234) | type Tagged struct
  type Remainder (line 239) | type Remainder struct
  type StructWithOmitEmpty (line 244) | type StructWithOmitEmpty struct
  type StructWithOmitZero (line 259) | type StructWithOmitZero struct
  type TypeConversionResult (line 274) | type TypeConversionResult struct
  function TestBasicTypes (line 308) | func TestBasicTypes(t *testing.T) {
  function TestBasic_IntWithFloat (line 412) | func TestBasic_IntWithFloat(t *testing.T) {
  function TestBasic_Merge (line 426) | func TestBasic_Merge(t *testing.T) {
  function TestBasic_Struct (line 450) | func TestBasic_Struct(t *testing.T) {
  function TestBasic_interfaceStruct (line 475) | func TestBasic_interfaceStruct(t *testing.T) {
  function TestBasic_interfaceStructNonPtr (line 497) | func TestBasic_interfaceStructNonPtr(t *testing.T) {
  function TestDecode_BasicSquash (line 518) | func TestDecode_BasicSquash(t *testing.T) {
  function TestDecodeFrom_BasicSquash (line 536) | func TestDecodeFrom_BasicSquash(t *testing.T) {
  function TestDecode_BasicJSONInline (line 566) | func TestDecode_BasicJSONInline(t *testing.T) {
  function TestDecodeFrom_BasicJSONInline (line 588) | func TestDecodeFrom_BasicJSONInline(t *testing.T) {
  function TestDecode_Embedded (line 622) | func TestDecode_Embedded(t *testing.T) {
  function TestDecode_EmbeddedPointer (line 648) | func TestDecode_EmbeddedPointer(t *testing.T) {
  function TestDecode_EmbeddedSlice (line 676) | func TestDecode_EmbeddedSlice(t *testing.T) {
  function TestDecode_EmbeddedArray (line 699) | func TestDecode_EmbeddedArray(t *testing.T) {
  function TestDecode_decodeSliceWithArray (line 722) | func TestDecode_decodeSliceWithArray(t *testing.T) {
  function TestDecode_EmbeddedNoSquash (line 737) | func TestDecode_EmbeddedNoSquash(t *testing.T) {
  function TestDecode_EmbeddedPointerNoSquash (line 760) | func TestDecode_EmbeddedPointerNoSquash(t *testing.T) {
  function TestDecode_EmbeddedSquash (line 786) | func TestDecode_EmbeddedSquash(t *testing.T) {
  function TestDecodeFrom_EmbeddedSquash (line 809) | func TestDecodeFrom_EmbeddedSquash(t *testing.T) {
  function TestDecode_EmbeddedPointerSquash_FromStructToMap (line 847) | func TestDecode_EmbeddedPointerSquash_FromStructToMap(t *testing.T) {
  function TestDecode_EmbeddedPointerSquash_FromMapToStruct (line 872) | func TestDecode_EmbeddedPointerSquash_FromMapToStruct(t *testing.T) {
  function TestDecode_EmbeddedPointerSquash_WithoutPreInitializedStructs_FromMapToStruct (line 897) | func TestDecode_EmbeddedPointerSquash_WithoutPreInitializedStructs_FromM...
  function TestDecode_EmbeddedPointerSquashWithNestedMapstructure_FromStructToMap (line 920) | func TestDecode_EmbeddedPointerSquashWithNestedMapstructure_FromStructTo...
  function TestDecode_EmbeddedPointerSquashWithNestedMapstructure_FromMapToStruct (line 953) | func TestDecode_EmbeddedPointerSquashWithNestedMapstructure_FromMapToStr...
  function TestDecode_EmbeddedSquashConfig (line 988) | func TestDecode_EmbeddedSquashConfig(t *testing.T) {
  function TestDecodeFrom_EmbeddedSquashConfig (line 1028) | func TestDecodeFrom_EmbeddedSquashConfig(t *testing.T) {
  function TestDecodeFrom_EmbeddedSquashConfig_WithTags (line 1084) | func TestDecodeFrom_EmbeddedSquashConfig_WithTags(t *testing.T) {
  function TestDecode_SquashOnNonStructType (line 1131) | func TestDecode_SquashOnNonStructType(t *testing.T) {
  function TestDecode_SquashOnInterfaceType (line 1147) | func TestDecode_SquashOnInterfaceType(t *testing.T) {
  function TestDecode_SquashOnOuterNestedInterfaceType (line 1174) | func TestDecode_SquashOnOuterNestedInterfaceType(t *testing.T) {
  function TestDecode_SquashOnInnerNestedInterfaceType (line 1218) | func TestDecode_SquashOnInnerNestedInterfaceType(t *testing.T) {
  function TestDecode_SquashOnNilInterfaceType (line 1266) | func TestDecode_SquashOnNilInterfaceType(t *testing.T) {
  function TestDecode_DecodeHook (line 1288) | func TestDecode_DecodeHook(t *testing.T) {
  function TestDecode_DecodeHookType (line 1324) | func TestDecode_DecodeHookType(t *testing.T) {
  function TestDecode_Nil (line 1361) | func TestDecode_Nil(t *testing.T) {
  function TestDecode_NilInterfaceHook (line 1379) | func TestDecode_NilInterfaceHook(t *testing.T) {
  function TestDecode_NilPointerHook (line 1415) | func TestDecode_NilPointerHook(t *testing.T) {
  function TestDecode_FuncHook (line 1452) | func TestDecode_FuncHook(t *testing.T) {
  function TestDecode_NonStruct (line 1488) | func TestDecode_NonStruct(t *testing.T) {
  function TestDecode_StructMatch (line 1507) | func TestDecode_StructMatch(t *testing.T) {
  function TestDecode_TypeConversion (line 1527) | func TestDecode_TypeConversion(t *testing.T) {
  function TestDecoder_ErrorUnused (line 1642) | func TestDecoder_ErrorUnused(t *testing.T) {
  function TestDecoder_ErrorUnused_NotSetable (line 1667) | func TestDecoder_ErrorUnused_NotSetable(t *testing.T) {
  function TestDecoder_ErrorUnset (line 1692) | func TestDecoder_ErrorUnset(t *testing.T) {
  function TestDecoder_ErrorUnset_AllowUnsetPointer (line 1717) | func TestDecoder_ErrorUnset_AllowUnsetPointer(t *testing.T) {
  function TestMap (line 1743) | func TestMap(t *testing.T) {
  function TestMapMerge (line 1781) | func TestMapMerge(t *testing.T) {
  function TestMapOfStruct (line 1813) | func TestMapOfStruct(t *testing.T) {
  function TestNestedType (line 1846) | func TestNestedType(t *testing.T) {
  function TestNestedTypePointer (line 1885) | func TestNestedTypePointer(t *testing.T) {
  function TestNestedTypeInterface (line 1925) | func TestNestedTypeInterface(t *testing.T) {
  function TestSlice (line 1974) | func TestSlice(t *testing.T) {
  function TestNotEmptyByteSlice (line 1996) | func TestNotEmptyByteSlice(t *testing.T) {
  function TestInvalidSlice (line 2024) | func TestInvalidSlice(t *testing.T) {
  function TestSliceOfStruct (line 2039) | func TestSliceOfStruct(t *testing.T) {
  function TestSliceCornerCases (line 2068) | func TestSliceCornerCases(t *testing.T) {
  function TestSliceToMap (line 2099) | func TestSliceToMap(t *testing.T) {
  function TestArray (line 2126) | func TestArray(t *testing.T) {
  function TestInvalidArray (line 2148) | func TestInvalidArray(t *testing.T) {
  function TestArrayOfStruct (line 2163) | func TestArrayOfStruct(t *testing.T) {
  function TestArrayToMap (line 2192) | func TestArrayToMap(t *testing.T) {
  function TestDecodeTable (line 2219) | func TestDecodeTable(t *testing.T) {
  function TestInvalidType (line 2634) | func TestInvalidType(t *testing.T) {
  function TestDecodeMetadata (line 2705) | func TestDecodeMetadata(t *testing.T) {
  function TestMetadata (line 2740) | func TestMetadata(t *testing.T) {
  function TestMetadata_Embedded (line 2798) | func TestMetadata_Embedded(t *testing.T) {
  function TestNonPtrValue (line 2836) | func TestNonPtrValue(t *testing.T) {
  function TestTagged (line 2849) | func TestTagged(t *testing.T) {
  function TestWeakDecode (line 2872) | func TestWeakDecode(t *testing.T) {
  function TestWeakDecodeMetadata (line 2896) | func TestWeakDecodeMetadata(t *testing.T) {
  function TestDecode_StructTaggedWithOmitempty_OmitEmptyValues (line 2936) | func TestDecode_StructTaggedWithOmitempty_OmitEmptyValues(t *testing.T) {
  function TestDecode_StructTaggedWithOmitempty_KeepNonEmptyValues (line 2961) | func TestDecode_StructTaggedWithOmitempty_KeepNonEmptyValues(t *testing....
  function TestDecode_StructTaggedWithOmitzero_KeepNonZeroValues (line 3005) | func TestDecode_StructTaggedWithOmitzero_KeepNonZeroValues(t *testing.T) {
  function TestDecode_StructTaggedWithOmitzero_DropZeroValues (line 3049) | func TestDecode_StructTaggedWithOmitzero_DropZeroValues(t *testing.T) {
  function TestDecode_mapToStruct (line 3087) | func TestDecode_mapToStruct(t *testing.T) {
  function TestDecoder_MatchName (line 3117) | func TestDecoder_MatchName(t *testing.T) {
  function TestDecoder_IgnoreUntaggedFields (line 3160) | func TestDecoder_IgnoreUntaggedFields(t *testing.T) {
  function TestDecoder_IgnoreUntaggedFieldsWithStruct (line 3200) | func TestDecoder_IgnoreUntaggedFieldsWithStruct(t *testing.T) {
  function TestDecoder_MultiTagInline (line 3238) | func TestDecoder_MultiTagInline(t *testing.T) {
  function TestDecoder_MultiTagRemain (line 3269) | func TestDecoder_MultiTagRemain(t *testing.T) {
  function TestDecoder_MultiTagBasic (line 3306) | func TestDecoder_MultiTagBasic(t *testing.T) {
  function TestDecoder_MultiTagPriority (line 3343) | func TestDecoder_MultiTagPriority(t *testing.T) {
  function TestDecoder_MultiTagWhitespace (line 3387) | func TestDecoder_MultiTagWhitespace(t *testing.T) {
  function TestDecoder_MultiTagEmptyNames (line 3420) | func TestDecoder_MultiTagEmptyNames(t *testing.T) {
  function TestDecoder_DecodeNilOption (line 3463) | func TestDecoder_DecodeNilOption(t *testing.T) {
  function TestDecoder_ExpandNilStructPointersHookFunc (line 3631) | func TestDecoder_ExpandNilStructPointersHookFunc(t *testing.T) {
  function TestErrorLeakageWeakDecode (line 3692) | func TestErrorLeakageWeakDecode(t *testing.T) {
  function testSliceInput (line 3749) | func testSliceInput(t *testing.T, input map[string]any, expected *Slice) {
  function testArrayInput (line 3777) | func testArrayInput(t *testing.T, input map[string]any, expected *Array) {
  type TestDatabaseConfig (line 3806) | type TestDatabaseConfig struct
  type TestServerConfig (line 3812) | type TestServerConfig struct
  function TestDecoder_ErrorUnused_EmbeddedStruct_QualifiedTypeName (line 3818) | func TestDecoder_ErrorUnused_EmbeddedStruct_QualifiedTypeName(t *testing...
  function TestDecode_structArrayDeepMap (line 3860) | func TestDecode_structArrayDeepMap(t *testing.T) {
  function TestDecoder_RootName (line 3903) | func TestDecoder_RootName(t *testing.T) {
  function stringPtr (line 3955) | func stringPtr(v string) *string  { return &v }
  function intPtr (line 3956) | func intPtr(v int) *int           { return &v }
  function uintPtr (line 3957) | func uintPtr(v uint) *uint        { return &v }
  function boolPtr (line 3958) | func boolPtr(v bool) *bool        { return &v }
  function floatPtr (line 3959) | func floatPtr(v float64) *float64 { return &v }
  function interfacePtr (line 3960) | func interfacePtr(v any) *any     { return &v }
  type TestMapFieldName (line 3962) | type TestMapFieldName struct
  function TestDecoder_MapFieldNameMapFromStruct (line 3967) | func TestDecoder_MapFieldNameMapFromStruct(t *testing.T) {
  function TestDecoder_MapFieldNameStructFromMap (line 4004) | func TestDecoder_MapFieldNameStructFromMap(t *testing.T) {
  function TestDecoder_MapFieldNameWithMatchName (line 4044) | func TestDecoder_MapFieldNameWithMatchName(t *testing.T) {
  function TestDecoder_MapFieldNameWithIgnoreUntaggedFields (line 4092) | func TestDecoder_MapFieldNameWithIgnoreUntaggedFields(t *testing.T) {
  type PersonWithUnmarshaler (line 4138) | type PersonWithUnmarshaler struct
    method UnmarshalMapstructure (line 4144) | func (p *PersonWithUnmarshaler) UnmarshalMapstructure(data any) error {
  function TestUnmarshaler_Basic (line 4159) | func TestUnmarshaler_Basic(t *testing.T) {
  function TestUnmarshaler_WithDecodeHook (line 4184) | func TestUnmarshaler_WithDecodeHook(t *testing.T) {
  function TestUnmarshaler_Disabled (line 4223) | func TestUnmarshaler_Disabled(t *testing.T) {
  type AddressWithUnmarshaler (line 4259) | type AddressWithUnmarshaler struct
    method UnmarshalMapstructure (line 4264) | func (a *AddressWithUnmarshaler) UnmarshalMapstructure(data any) error {
  type PersonWithAddress (line 4278) | type PersonWithAddress struct
  function TestUnmarshaler_Nested (line 4283) | func TestUnmarshaler_Nested(t *testing.T) {
  type SquashInnerNoUnmarshaler (line 4313) | type SquashInnerNoUnmarshaler struct
  type PersonWithSquashedNoUnmarshaler (line 4319) | type PersonWithSquashedNoUnmarshaler struct
  function TestUnmarshaler_SquashWorksNormally (line 4324) | func TestUnmarshaler_SquashWorksNormally(t *testing.T) {
  type EmbeddedWithUnmarshaler (line 4348) | type EmbeddedWithUnmarshaler struct
    method UnmarshalMapstructure (line 4354) | func (e *EmbeddedWithUnmarshaler) UnmarshalMapstructure(data any) error {
  type PersonWithSquashedEmbedded (line 4369) | type PersonWithSquashedEmbedded struct
  function TestUnmarshaler_EmbeddingInheritsInterface (line 4374) | func TestUnmarshaler_EmbeddingInheritsInterface(t *testing.T) {
  type FailingUnmarshaler (line 4402) | type FailingUnmarshaler struct
    method UnmarshalMapstructure (line 4406) | func (f *FailingUnmarshaler) UnmarshalMapstructure(data any) error {
  function TestUnmarshaler_ErrorHandling (line 4410) | func TestUnmarshaler_ErrorHandling(t *testing.T) {
  type PointerReceiverUnmarshaler (line 4431) | type PointerReceiverUnmarshaler struct
    method UnmarshalMapstructure (line 4435) | func (p *PointerReceiverUnmarshaler) UnmarshalMapstructure(data any) e...
  function TestUnmarshaler_PointerReceiver (line 4446) | func TestUnmarshaler_PointerReceiver(t *testing.T) {
  function TestUnmarshaler_Metadata (line 4464) | func TestUnmarshaler_Metadata(t *testing.T) {
  type PersonWithNonSquashedEmbedded (line 4495) | type PersonWithNonSquashedEmbedded struct
  function TestUnmarshaler_NonSquashedEmbedded (line 4500) | func TestUnmarshaler_NonSquashedEmbedded(t *testing.T) {
  type CustomString (line 4530) | type CustomString
    method UnmarshalMapstructure (line 4532) | func (c *CustomString) UnmarshalMapstructure(data any) error {
  function TestUnmarshaler_NonStructType (line 4541) | func TestUnmarshaler_NonStructType(t *testing.T) {
  type CustomSlice (line 4564) | type CustomSlice
    method UnmarshalMapstructure (line 4566) | func (c *CustomSlice) UnmarshalMapstructure(data any) error {
  function TestUnmarshaler_SliceType (line 4584) | func TestUnmarshaler_SliceType(t *testing.T) {
  type CustomMap (line 4608) | type CustomMap
    method UnmarshalMapstructure (line 4610) | func (c *CustomMap) UnmarshalMapstructure(data any) error {
  function TestUnmarshaler_MapType (line 4624) | func TestUnmarshaler_MapType(t *testing.T) {
  function TestUnmarshaler_StructToMap (line 4652) | func TestUnmarshaler_StructToMap(t *testing.T) {

FILE: reflect_go1_19.go
  function isComparable (line 7) | func isComparable(v reflect.Value) bool {

FILE: reflect_go1_20.go
  function isComparable (line 8) | func isComparable(v reflect.Value) bool {
Condensed preview — 27 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (340K chars).
[
  {
    "path": ".editorconfig",
    "chars": 302,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_newline = true\ntrim_"
  },
  {
    "path": ".envrc",
    "chars": 91,
    "preview": "#!/usr/bin/env bash\n\nexport DIRENV_WARN_TIMEOUT=20s\n\neval \"$(devenv direnvrc)\"\n\nuse devenv\n"
  },
  {
    "path": ".github/.editorconfig",
    "chars": 33,
    "preview": "[{*.yml,*.yaml}]\nindent_size = 2\n"
  },
  {
    "path": ".github/dependabot.yaml",
    "chars": 195,
    "preview": "version: 2\n\nupdates:\n  - package-ecosystem: gomod\n    directory: /\n    schedule:\n      interval: daily\n\n  - package-ecos"
  },
  {
    "path": ".github/workflows/analysis-scorecard.yaml",
    "chars": 1179,
    "preview": "name: OpenSSF Scorecard\n\non:\n  branch_protection_rule:\n  push:\n    branches: [main]\n  schedule:\n    - cron: \"30 0 * * 5\""
  },
  {
    "path": ".github/workflows/ci.yaml",
    "chars": 1254,
    "preview": "name: CI\n\non:\n  push:\n    branches: [main]\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  test:\n    name: Test\n"
  },
  {
    "path": ".gitignore",
    "chars": 106,
    "preview": "/bin/\n/build/\n/var/\n\n# Devenv\n.devenv*\ndevenv.local.nix\ndevenv.local.yaml\n.direnv\n.pre-commit-config.yaml\n"
  },
  {
    "path": ".golangci.yaml",
    "chars": 773,
    "preview": "version: \"2\"\n\nrun:\n  timeout: 10m\n\nlinters:\n  enable:\n    - govet\n    - ineffassign\n    # - misspell\n    - nolintlint\n  "
  },
  {
    "path": "CHANGELOG.md",
    "chars": 3221,
    "preview": "> [!WARNING]\n> As of v2 of this library, change log can be found in GitHub releases.\n\n## 1.5.1\n\n* Wrap errors so they're"
  },
  {
    "path": "LICENSE",
    "chars": 1085,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2013 Mitchell Hashimoto\n\nPermission is hereby granted, free of charge, to any perso"
  },
  {
    "path": "README.md",
    "chars": 3647,
    "preview": "# mapstructure\n\n[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/go-viper/mapstructure/c"
  },
  {
    "path": "decode_hooks.go",
    "chars": 19432,
    "preview": "package mapstructure\n\nimport (\n\t\"encoding\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strin"
  },
  {
    "path": "decode_hooks_test.go",
    "chars": 77699,
    "preview": "package mapstructure\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"math/big\"\n\t\"net\"\n\t\"net/netip\"\n\t\"net/url\"\n\t\"re"
  },
  {
    "path": "devenv.nix",
    "chars": 117,
    "preview": "{\n  pkgs,\n  ...\n}:\n\n{\n  languages = {\n    go.enable = true;\n  };\n\n  packages = with pkgs; [\n    golangci-lint\n  ];\n}\n"
  },
  {
    "path": "devenv.yaml",
    "chars": 134,
    "preview": "# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json\ninputs:\n  nixpkgs:\n    url: github:cachix/devenv-ni"
  },
  {
    "path": "errors.go",
    "chars": 4720,
    "preview": "package mapstructure\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Error i"
  },
  {
    "path": "go.mod",
    "chars": 52,
    "preview": "module github.com/go-viper/mapstructure/v2\n\ngo 1.18\n"
  },
  {
    "path": "internal/errors/errors.go",
    "chars": 170,
    "preview": "package errors\n\nimport \"errors\"\n\nfunc New(text string) error {\n\treturn errors.New(text)\n}\n\nfunc As(err error, target int"
  },
  {
    "path": "internal/errors/join.go",
    "chars": 116,
    "preview": "//go:build go1.20\n\npackage errors\n\nimport \"errors\"\n\nfunc Join(errs ...error) error {\n\treturn errors.Join(errs...)\n}\n"
  },
  {
    "path": "internal/errors/join_go1_19.go",
    "chars": 1365,
    "preview": "//go:build !go1.20\n\n// Copyright 2022 The Go Authors. All rights reserved.\n// Use of this source code is governed by a B"
  },
  {
    "path": "mapstructure.go",
    "chars": 55732,
    "preview": "// Package mapstructure exposes functionality to convert one arbitrary\n// Go type into another, typically to convert a m"
  },
  {
    "path": "mapstructure_benchmark_test.go",
    "chars": 6034,
    "preview": "package mapstructure\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n)\n\ntype Person struct {\n\tName   string\n\tAge    int\n\tEmails []"
  },
  {
    "path": "mapstructure_bugs_test.go",
    "chars": 15296,
    "preview": "package mapstructure\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n)\n\n// GH-1, GH-10, GH-96\nfunc TestDecode_NilValue(t *testin"
  },
  {
    "path": "mapstructure_examples_test.go",
    "chars": 9534,
    "preview": "package mapstructure\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nfunc ExampleDecode() {\n\ttype Person struct {\n\t"
  },
  {
    "path": "mapstructure_test.go",
    "chars": 99563,
    "preview": "package mapstructure\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"reflect\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n"
  },
  {
    "path": "reflect_go1_19.go",
    "chars": 799,
    "preview": "//go:build !go1.20\n\npackage mapstructure\n\nimport \"reflect\"\n\nfunc isComparable(v reflect.Value) bool {\n\tk := v.Kind()\n\tsw"
  },
  {
    "path": "reflect_go1_20.go",
    "chars": 176,
    "preview": "//go:build go1.20\n\npackage mapstructure\n\nimport \"reflect\"\n\n// TODO: remove once we drop support for Go <1.20\nfunc isComp"
  }
]

About this extraction

This page contains the full source code of the go-viper/mapstructure GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 27 files (295.7 KB), approximately 91.7k tokens, and a symbol index with 408 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!