diff --git a/viper.go b/viper.go index 6307c3c..8712dd5 100644 --- a/viper.go +++ b/viper.go @@ -865,13 +865,27 @@ func UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) e return v.UnmarshalKey(key, rawVal, opts...) } func (v *Viper) UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error { - err := decode(v.Get(key), defaultDecoderConfig(rawVal, opts...)) + lcaseKey := strings.ToLower(key) - if err != nil { - return err + // AllSettings returns settings from every sources merged into one tree + settings := v.AllSettings() + + keyParts := strings.Split(lcaseKey, v.keyDelim) + for i := 0; i < len(keyParts)-1; i++ { + if value, found := settings[keyParts[i]]; found { + if valueMap, ok := value.(map[string]interface{}); ok { + settings = valueMap + continue + } + // if the current value is not a map[string]interface{} we most likely reach a + // leaf and the key/path is wrong + return fmt.Errorf("unknown key %s", lcaseKey) + } else { + return fmt.Errorf("unknown key %s", lcaseKey) + } } - - return nil + finalSetting := settings[keyParts[len(keyParts)-1]] + return decode(finalSetting, defaultDecoderConfig(rawVal, opts...)) } // Unmarshal unmarshals the config into a Struct. Make sure that the tags diff --git a/viper_test.go b/viper_test.go index cbf2ff0..73af065 100644 --- a/viper_test.go +++ b/viper_test.go @@ -1725,6 +1725,48 @@ func TestParseNested(t *testing.T) { assert.Equal(t, 200*time.Millisecond, items[0].Nested.Delay) } +func TestUnmarshalKey(t *testing.T) { + type testStruct struct { + Delay int `mapstructure:"delay" yaml:"delay"` + Port int `mapstructure:"port" yaml:"port"` + Host string `mapstructure:"host" yaml:"host"` + Items []string `mapstructure:"items" yaml:"items"` + } + + config := ` +level_1: + level_2: + port: 1234 + items: + - "test 1" +` + v := New() + v.SetDefault("level_1.level_2.delay", 50) + v.SetDefault("level_1.level_2.port", 9999) + v.SetDefault("level_1.level_2.host", "default") + v.SetDefault("level_1.level_2.items", []string{}) + + // Use env vars for some settings + v.BindEnv("level_1.level_2.host", "DD_TEST_HOST") + t.Setenv("DD_TEST_HOST", "dd.com") + + initConfig(v, "yaml", config) + + // manually overwrite some settings + v.Set("level_1.level_2.items", []string{"test_2", "test_3"}) + + data := testStruct{} + err := v.UnmarshalKey("level_1.level_2", &data) + if err != nil { + t.Fatalf("unable to decode into struct, %v", err) + } + + assert.Equal(t, 50, data.Delay) // value from defaults + assert.Equal(t, 1234, data.Port) // value from config + assert.Equal(t, "dd.com", data.Host) // value from env + assert.Equal(t, []string{"test_2", "test_3"}, data.Items) // value from Set() +} + func doTestCaseInsensitive(t *testing.T, typ, config string) { v := New() initConfig(v, typ, config)