Repository: spf13/viper
Pull request #2005 (2025-03-26 06:36:52)

Backport config type fixes to 1.20.x
See #2003


Patch:
diff --git a/viper.go b/viper.go
index f900e58..a58d757 100644
--- a/viper.go
+++ b/viper.go
@@ -1535,27 +1535,29 @@ func (v *Viper) MergeInConfig() error {
 func ReadConfig(in io.Reader) error { return v.ReadConfig(in) }
 
 func (v *Viper) ReadConfig(in io.Reader) error {
-	if v.configType == "" {
-		return errors.New("cannot decode configuration: config type is not set")
+	config := make(map[string]any)
+
+	err := v.unmarshalReader(in, config)
+	if err != nil {
+		return err
 	}
 
-	v.config = make(map[string]any)
-	return v.unmarshalReader(in, v.config)
+	v.config = config
+
+	return nil
 }
 
 // MergeConfig merges a new configuration with an existing config.
 func MergeConfig(in io.Reader) error { return v.MergeConfig(in) }
 
 func (v *Viper) MergeConfig(in io.Reader) error {
-	if v.configType == "" {
-		return errors.New("cannot decode configuration: config type is not set")
-	}
+	config := make(map[string]any)
 
-	cfg := make(map[string]any)
-	if err := v.unmarshalReader(in, cfg); err != nil {
+	if err := v.unmarshalReader(in, config); err != nil {
 		return err
 	}
-	return v.MergeConfigMap(cfg)
+
+	return v.MergeConfigMap(config)
 }
 
 // MergeConfigMap merges the configuration from the map given with an existing config.
@@ -1662,15 +1664,21 @@ func (v *Viper) writeConfig(filename string, force bool) error {
 }
 
 func (v *Viper) unmarshalReader(in io.Reader, c map[string]any) error {
+	format := strings.ToLower(v.getConfigType())
+	if format == "" {
+		return errors.New("cannot decode configuration: unable to determine config type")
+	}
+
 	buf := new(bytes.Buffer)
 	buf.ReadFrom(in)
 
-	format := strings.ToLower(v.getConfigType())
-
+	// TODO: remove this once SupportedExts is deprecated/removed
 	if !slices.Contains(SupportedExts, format) {
 		return UnsupportedConfigError(format)
 	}
 
+	// TODO: return [UnsupportedConfigError] if the registry does not contain the format
+	// TODO: consider deprecating this error type
 	decoder, err := v.decoderRegistry.Decoder(format)
 	if err != nil {
 		return ConfigParseError{err}


Test patch:
diff --git a/viper_test.go b/viper_test.go
index c0c0e7c..f7e0787 100644
--- a/viper_test.go
+++ b/viper_test.go
@@ -1542,6 +1542,14 @@ func TestReadConfig(t *testing.T) {
 	})
 }
 
+func TestReadConfigWithSetConfigFile(t *testing.T) {
+	v := New()
+	v.SetConfigFile("config.yaml") // Dummy value to infer config type from file extension
+	err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleSrc))
+	require.NoError(t, err)
+	assert.Equal(t, 45000, v.GetInt("hello.pop"))
+}
+
 func TestIsSet(t *testing.T) {
 	v := New()
 	v.SetConfigType("yaml")
@@ -2059,6 +2067,14 @@ func TestMergeConfig(t *testing.T) {
 	assert.Equal(t, "bar", v.GetString("fu"))
 }
 
+func TestMergeConfigWithSetConfigFile(t *testing.T) {
+	v := New()
+	v.SetConfigFile("config.yaml") // Dummy value to infer config type from file extension
+	err := v.MergeConfig(bytes.NewBuffer(yamlMergeExampleSrc))
+	require.NoError(t, err)
+	assert.Equal(t, 45000, v.GetInt("hello.pop"))
+}
+
 func TestMergeConfigOverrideType(t *testing.T) {
 	v := New()
 	v.SetConfigType("json")