Repository: spf13/viper
Pull request #1715 (2023-12-15 12:31:58)

feat!: hide struct binding behind a feature flag

github-actions[bot]: 👋 Thanks for contributing to Viper! You are awesome! 🎉

A maintainer will take a look at your pull request shortly. 👀

In the meantime: We are working on **Viper v2** and we would love to hear your thoughts about what you like or don't like about Viper, so we can improve or fix those issues.

⏰ If you have a couple minutes, please take some time and share your thoughts: https://forms.gle/R6faU74qPRPAzchZ9

📣 If you've already given us your feedback, you can still help by spreading the news,
either by sharing the above link or telling people about this on Twitter:

https://twitter.com/sagikazarmark/status/1306904078967074816

**Thank you!** ❤️


Patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index e641b04..74ef951 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -26,7 +26,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
         with:
-          go-version: '1.21'
+          go-version: "1.21"
 
       - name: Build
         run: go build .
@@ -44,8 +44,8 @@ jobs:
       fail-fast: false
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
-        go: ['1.19', '1.20', '1.21']
-        tags: ['', 'finder']
+        go: ["1.19", "1.20", "1.21"]
+        tags: ["", "finder", "viper_bind_struct"]
 
     steps:
       - name: Checkout repository
@@ -75,7 +75,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
         with:
-          go-version: '1.21'
+          go-version: "1.21"
 
       - name: Lint
         uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0
diff --git a/internal/features/bind_struct.go b/internal/features/bind_struct.go
new file mode 100644
index 0000000..89302c2
--- /dev/null
+++ b/internal/features/bind_struct.go
@@ -0,0 +1,5 @@
+//go:build viper_bind_struct
+
+package features
+
+const BindStruct = true
diff --git a/internal/features/bind_struct_default.go b/internal/features/bind_struct_default.go
new file mode 100644
index 0000000..edfaf73
--- /dev/null
+++ b/internal/features/bind_struct_default.go
@@ -0,0 +1,5 @@
+//go:build !viper_bind_struct
+
+package features
+
+const BindStruct = false
diff --git a/viper.go b/viper.go
index 122280b..20eb4da 100644
--- a/viper.go
+++ b/viper.go
@@ -48,6 +48,7 @@ import (
 	"github.com/spf13/viper/internal/encoding/json"
 	"github.com/spf13/viper/internal/encoding/toml"
 	"github.com/spf13/viper/internal/encoding/yaml"
+	"github.com/spf13/viper/internal/features"
 )
 
 // ConfigMarshalError happens when failing to marshal the configuration.
@@ -1114,14 +1115,20 @@ func Unmarshal(rawVal any, opts ...DecoderConfigOption) error {
 }
 
 func (v *Viper) Unmarshal(rawVal any, opts ...DecoderConfigOption) error {
-	// TODO: make this optional?
-	structKeys, err := v.decodeStructKeys(rawVal, opts...)
-	if err != nil {
-		return err
+	keys := v.AllKeys()
+
+	if features.BindStruct {
+		// TODO: make this optional?
+		structKeys, err := v.decodeStructKeys(rawVal, opts...)
+		if err != nil {
+			return err
+		}
+
+		keys = append(keys, structKeys...)
 	}
 
 	// TODO: struct keys should be enough?
-	return decode(v.getSettings(append(v.AllKeys(), structKeys...)), defaultDecoderConfig(rawVal, opts...))
+	return decode(v.getSettings(keys), defaultDecoderConfig(rawVal, opts...))
 }
 
 func (v *Viper) decodeStructKeys(input any, opts ...DecoderConfigOption) ([]string, error) {
@@ -1179,14 +1186,20 @@ func (v *Viper) UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error {
 	config := defaultDecoderConfig(rawVal, opts...)
 	config.ErrorUnused = true
 
-	// TODO: make this optional?
-	structKeys, err := v.decodeStructKeys(rawVal, opts...)
-	if err != nil {
-		return err
+	keys := v.AllKeys()
+
+	if features.BindStruct {
+		// TODO: make this optional?
+		structKeys, err := v.decodeStructKeys(rawVal, opts...)
+		if err != nil {
+			return err
+		}
+
+		keys = append(keys, structKeys...)
 	}
 
 	// TODO: struct keys should be enough?
-	return decode(v.getSettings(append(v.AllKeys(), structKeys...)), config)
+	return decode(v.getSettings(keys), config)
 }
 
 // BindPFlags binds a full flag set to the configuration, using each flag's long


Test patch:
diff --git a/viper_test.go b/viper_test.go
index 4dc52ac..0b1f407 100644
--- a/viper_test.go
+++ b/viper_test.go
@@ -28,6 +28,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
+	"github.com/spf13/viper/internal/features"
 	"github.com/spf13/viper/internal/testutil"
 )
 
@@ -956,6 +957,10 @@ func TestUnmarshalWithDecoderOptions(t *testing.T) {
 }
 
 func TestUnmarshalWithAutomaticEnv(t *testing.T) {
+	if !features.BindStruct {
+		t.Skip("binding struct is not enabled")
+	}
+
 	t.Setenv("PORT", "1313")
 	t.Setenv("NAME", "Steve")
 	t.Setenv("DURATION", "1s1ms")