Skip to content

Struct binding from environment variables does not decode nested structs #2001

@jalaziz

Description

@jalaziz

Preflight Checklist

  • I have searched the issue tracker for an issue that matches the one I want to file, without success.
  • I am not looking for support or already pursued the available support channels without success.
  • I have checked the troubleshooting guide for my problem, without success.

Viper Version

1.20

Go Version

1.24.1

Config Source

Environment variables

Format

Other (specify below)

Repl.it link

https://replit.com/@jameel4/Viper-Struct-Unmarshaling?v=1

Code reproducing the issue

package main

import (
	"encoding/json"
	"fmt"
	"os"
	"reflect"
	"strings"

	"github.com/go-viper/mapstructure/v2"
	"github.com/spf13/viper"
)

func jsonStringUnmarshallerHookFunc() mapstructure.DecodeHookFuncValue {
	return func(f reflect.Value, t reflect.Value) (any, error) {
		data := f.Interface()
		if f.Kind() != reflect.String || t.Kind() == reflect.String {
			return data, nil
		}
		raw := data.(string)
		if raw != "" && json.Valid([]byte(raw)) {
			var m any
			err := json.Unmarshal([]byte(raw), &m)
			return m, err
		}
		return data, nil
	}
}

type Config struct {
	Foo    string
	Nested struct {
		Bar string
	}
}

func main() {
	os.Setenv("FOO", "test")
	os.Setenv("NESTED", `{"bar": "test2"}`)

	var config Config
	v := viper.NewWithOptions(
		viper.ExperimentalBindStruct(),
		viper.WithDecodeHook(jsonStringUnmarshallerHookFunc()),
	)
	v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
	v.AutomaticEnv()

	if err := v.Unmarshal(&config); err != nil {
		fmt.Println("Unable to unmarshal config")
	}
	fmt.Printf("%+v\n", config)

	if err := v.UnmarshalKey("nested", &config.Nested); err != nil {
		fmt.Println("Unable to unmarshal config")
	}
	fmt.Printf("%+v\n", config)
}

Expected Behavior

I expect the initial call to Unmarshal to correctly decode the NESTED environment variable into the Nested struct.

{Foo:test Nested:{Bar:test2}}
{Foo:test Nested:{Bar:test2}}

Actual Behavior

The NESTED environment variable in not decoded when calling Unmarshal, but it properly decoded when calling UnmarshalKey.

{Foo:test Nested:{Bar:}}
{Foo:test Nested:{Bar:test2}}

Steps To Reproduce

No response

Additional Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions