Unmarshal() does not preserve nested configuration when all fields are YAML null
Author: sk-wangwyCreated Sep 3, 2026Updated Sep 3, 2026
Labelskind/bug
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
v1.20.0
Go Version
1.24.0
Config Source
Files
Format
YAML
Repl.it link
No response
Code reproducing the issue
package main
import (
"bytes"
"fmt"
"github.com/spf13/viper"
)
type clientConfig struct {
Protocol string `mapstructure:"protocol"`
Addr string `mapstructure:"addr"`
}
type Config struct {
Clients map[string]clientConfig `mapstructure:"clients"`
}
func main() {
data := `
clients:
mysql:
protocol:
addr:
`
v := viper.New()
v.SetConfigType("yaml")
if err := v.ReadConfig(bytes.NewBufferString(data)); err != nil {
panic(err)
}
var cfg Config
if err := v.Unmarshal(&cfg); err != nil {
panic(err)
}
fmt.Printf("%#v\n", cfg.Clients)
}Expected Behavior
Output: map[string]main.clientConfig{ "mysql": {}, }
Actual Behavior
Output: map[string]main.clientConfig(nil)
Steps To Reproduce
- Use the YAML configuration shown above.
- Call
v.Unmarshal(&cfg). - Check
cfg.Clients.
Additional Information
The issue appears to occur in AllSettings(), before the configuration is passed to mapstructure for unmarshaling.
v.Get("clients")correctly returns themysqlentry withnilvalues.v.Get("clients.mysql")also correctly returns the configuration.- However,
v.AllSettings()omits theclientssection entirely. - As a result,
Unmarshal()has noclients.mysqlentry to decode into the destination map.
I could not find a decoder option that preserves this entry, since the entry is already missing from AllSettings() before decoding occurs.
Source: spf13/viper