This document covers advanced Viper features including configuration options, custom codecs, finders, unmarshaling, and experimental features.
type Option interface {
// Has unexported methods
}Configures Viper using the functional options paradigm. Options are passed to NewWithOptions() to customize Viper instance behavior.
func NewWithOptions(opts ...Option) *ViperCreates a new Viper instance with configuration options.
func SetOptions(opts ...Option)
func (*Viper) SetOptions(opts ...Option)Sets options on an existing Viper instance (or the global instance).
Warning: Subsequent calls may override previously set options. It's better to use a local Viper instance with NewWithOptions.
func KeyDelimiter(d string) OptionSets the delimiter used for determining key parts. By default the value is ".".
Example:
v := viper.NewWithOptions(viper.KeyDelimiter("::"))
v.Set("database::host", "localhost")
host := v.GetString("database::host")func EnvKeyReplacer(r StringReplacer) OptionSets a replacer used for mapping environment variables to internal keys.
type StringReplacer interface {
// Replace returns a copy of s with all replacements performed
Replace(s string) string
}Example:
type customReplacer struct{}
func (c customReplacer) Replace(s string) string {
return strings.ReplaceAll(s, "-", "_")
}
v := viper.NewWithOptions(viper.EnvKeyReplacer(customReplacer{}))func WithLogger(l *slog.Logger) OptionSets a custom logger for Viper to use.
Example:
import "log/slog"
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
v := viper.NewWithOptions(viper.WithLogger(logger))func WithFinder(f Finder) OptionSets a custom Finder for locating configuration files.
type Finder interface {
Find(fsys afero.Fs) ([]string, error)
}func Finders(finders ...Finder) FinderCombines multiple finders into one.
func WithCodecRegistry(r CodecRegistry) OptionSets a custom EncoderRegistry and DecoderRegistry.
func WithEncoderRegistry(r EncoderRegistry) OptionSets a custom EncoderRegistry.
func WithDecoderRegistry(r DecoderRegistry) OptionSets a custom DecoderRegistry.
func WithDecodeHook(h mapstructure.DecodeHookFunc) OptionSets a default decode hook for mapstructure (used in unmarshaling).
The codec system allows customization of how configuration files are encoded and decoded.
type Encoder interface {
Encode(v map[string]any) ([]byte, error)
}Encodes Viper's internal data structures into a byte representation. Primarily used for encoding a map[string]any into a file format.
type Decoder interface {
Decode(b []byte, v map[string]any) error
}Decodes the contents of a byte slice into Viper's internal data structures. Primarily used for decoding contents of a file into a map[string]any.
type Codec interface {
Encoder
Decoder
}Combines Encoder and Decoder interfaces.
type EncoderRegistry interface {
Encoder(format string) (Encoder, error)
}Returns an Encoder for a given format. Format is case-insensitive. Returns an error if no Encoder is registered for the format.
type DecoderRegistry interface {
Decoder(format string) (Decoder, error)
}Returns a Decoder for a given format. Format is case-insensitive. Returns an error if no Decoder is registered for the format.
type CodecRegistry interface {
EncoderRegistry
DecoderRegistry
}Combines EncoderRegistry and DecoderRegistry interfaces.
type DefaultCodecRegistry struct {
// Has unexported fields
}Simple implementation of CodecRegistry that allows registering custom Codecs.
func NewCodecRegistry() *DefaultCodecRegistryReturns a new CodecRegistry, ready to accept custom Codecs.
func (*DefaultCodecRegistry) RegisterCodec(format string, codec Codec) errorRegisters a custom Codec. Format is case-insensitive.
func (*DefaultCodecRegistry) Encoder(format string) (Encoder, error)Implements the EncoderRegistry interface. Format is case-insensitive.
func (*DefaultCodecRegistry) Decoder(format string) (Decoder, error)Implements the DecoderRegistry interface. Format is case-insensitive.
Example:
// Custom codec for a special format
type MyCodec struct{}
func (c MyCodec) Encode(v map[string]any) ([]byte, error) {
// Custom encoding logic
return json.Marshal(v)
}
func (c MyCodec) Decode(b []byte, v map[string]any) error {
// Custom decoding logic
return json.Unmarshal(b, &v)
}
// Register custom codec
registry := viper.NewCodecRegistry()
registry.RegisterCodec("myformat", MyCodec{})
v := viper.NewWithOptions(viper.WithCodecRegistry(registry))
v.SetConfigType("myformat")Viper can unmarshal configuration into Go structs.
func Unmarshal(rawVal any, opts ...DecoderConfigOption) error
func (*Viper) Unmarshal(rawVal any, opts ...DecoderConfigOption) errorUnmarshals the config into a Struct. Make sure that the tags on the fields of the structure are properly set.
func UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error
func (*Viper) UnmarshalExact(rawVal any, opts ...DecoderConfigOption) errorUnmarshals the config into a Struct, erroring if a field is nonexistent in the destination struct.
func UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) error
func (*Viper) UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) errorTakes a single key and unmarshals it into a Struct.
type DecoderConfigOption func(*mapstructure.DecoderConfig)A function type that can be passed to Unmarshal methods to configure mapstructure.DecoderConfig options.
func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOptionReturns a DecoderConfigOption which overrides the default DecodeHook value.
Default decode hook:
mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToSliceHookFunc(","),
)Basic unmarshaling:
type Config struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Username string `mapstructure:"username"`
}
var config Config
err := viper.Unmarshal(&config)
if err != nil {
log.Fatal("Unable to unmarshal config:", err)
}
fmt.Printf("Host: %s, Port: %d\n", config.Host, config.Port)Unmarshaling nested structures:
type DatabaseConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
}
type AppConfig struct {
Name string `mapstructure:"name"`
Debug bool `mapstructure:"debug"`
Database DatabaseConfig `mapstructure:"database"`
}
var config AppConfig
viper.Unmarshal(&config)Unmarshaling a specific key:
type DatabaseConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
}
var dbConfig DatabaseConfig
err := viper.UnmarshalKey("database", &dbConfig)Using custom decode hooks:
import "github.com/go-viper/mapstructure/v2"
type Config struct {
Timeout time.Duration `mapstructure:"timeout"`
}
hook := mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToSliceHookFunc(","),
)
var config Config
err := viper.Unmarshal(&config, viper.DecodeHook(hook))Using UnmarshalExact for strict validation:
type Config struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
}
// Config file has extra fields: host, port, debug
// UnmarshalExact will error because 'debug' is not in the struct
var config Config
err := viper.UnmarshalExact(&config)
if err != nil {
// Error: 'debug' is not a field in Config
}func ExperimentalBindStruct() OptionTells Viper to use the new bind struct feature. This is an experimental feature that may change in future versions.
Example:
v := viper.NewWithOptions(viper.ExperimentalBindStruct())func ExperimentalFinder() OptionTells Viper to use the new Finder interface for finding configuration files. This is an experimental feature.
Example:
v := viper.NewWithOptions(viper.ExperimentalFinder())func Sub(key string) *Viper
func (*Viper) Sub(key string) *ViperReturns a new Viper instance representing a sub tree of the current instance. Sub is case-insensitive for a key.
Example:
// Configuration:
// {
// "database": {
// "host": "localhost",
// "port": 5432,
// "credentials": {
// "username": "admin",
// "password": "secret"
// }
// }
// }
// Get database config as sub-tree
dbViper := viper.Sub("database")
if dbViper == nil {
log.Fatal("Database config not found")
}
host := dbViper.GetString("host")
port := dbViper.GetInt("port")
// Get credentials as nested sub-tree
credsViper := dbViper.Sub("credentials")
username := credsViper.GetString("username")
password := credsViper.GetString("password")Use cases:
func Debug()
func (*Viper) Debug()Prints all configuration registries for debugging purposes.
func DebugTo(w io.Writer)
func (*Viper) DebugTo(w io.Writer)Prints debug information to the specified writer.
Example:
// Print to stdout
viper.Debug()
// Print to file
f, _ := os.Create("debug.txt")
defer f.Close()
viper.DebugTo(f)
// Print to buffer
var buf bytes.Buffer
viper.DebugTo(&buf)
fmt.Println(buf.String())func Reset()Resets all configuration to default settings. Intended for testing. Available in the public interface so applications can use it in their testing as well.
Example:
func TestConfig(t *testing.T) {
// Set up test configuration
viper.Set("test_key", "test_value")
// Run test
// ...
// Clean up
viper.Reset()
}package main
import (
"fmt"
"log"
"log/slog"
"os"
"time"
"github.com/go-viper/mapstructure/v2"
"github.com/spf13/viper"
)
// Custom configuration struct
type AppConfig struct {
Name string `mapstructure:"name"`
Debug bool `mapstructure:"debug"`
Timeout time.Duration `mapstructure:"timeout"`
Database DatabaseConfig `mapstructure:"database"`
Features []string `mapstructure:"features"`
}
type DatabaseConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
MaxConns int `mapstructure:"max_connections"`
}
// Custom string replacer
type dashToUnderscore struct{}
func (dashToUnderscore) Replace(s string) string {
return strings.ReplaceAll(s, "-", "_")
}
func main() {
// Create custom logger
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
// Custom decode hook for durations
durationHook := mapstructure.StringToTimeDurationHookFunc()
// Create Viper with options
v := viper.NewWithOptions(
viper.WithLogger(logger),
viper.KeyDelimiter("."),
viper.EnvKeyReplacer(dashToUnderscore{}),
viper.WithDecodeHook(durationHook),
)
// Set defaults
v.SetDefault("name", "myapp")
v.SetDefault("debug", false)
v.SetDefault("timeout", "30s")
v.SetDefault("database.max_connections", 10)
// Read configuration
v.SetConfigName("config")
v.SetConfigType("yaml")
v.AddConfigPath(".")
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
log.Fatal(err)
}
}
// Enable environment variables
v.SetEnvPrefix("app")
v.AutomaticEnv()
// Unmarshal into struct
var config AppConfig
if err := v.Unmarshal(&config); err != nil {
log.Fatal("Unable to unmarshal config:", err)
}
fmt.Printf("App: %s (debug: %v)\n", config.Name, config.Debug)
fmt.Printf("Timeout: %v\n", config.Timeout)
fmt.Printf("Database: %s:%d (max conns: %d)\n",
config.Database.Host,
config.Database.Port,
config.Database.MaxConns,
)
// Work with sub-trees
dbViper := v.Sub("database")
if dbViper != nil {
fmt.Println("Database host from sub-tree:", dbViper.GetString("host"))
}
// Debug output
v.Debug()
}