[teamai] Push 87 resource(s) from XingfenD
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
# Viper Unmarshal and Struct Mapping
|
||||
|
||||
## Basic Unmarshal
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Port int `mapstructure:"port"`
|
||||
Host string `mapstructure:"host"`
|
||||
LogLevel string `mapstructure:"log_level"`
|
||||
Database struct {
|
||||
DSN string `mapstructure:"dsn"`
|
||||
MaxConn int `mapstructure:"max_conn"`
|
||||
} `mapstructure:"database"`
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
if err := viper.Unmarshal(&cfg); err != nil {
|
||||
return fmt.Errorf("decoding config: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
## mapstructure tags
|
||||
|
||||
Always use `mapstructure` tags. Without them, mapstructure falls back to case-insensitive field name matching, which works for simple cases but silently fails for:
|
||||
|
||||
- Nested structs where the outer key uses an underscore (`max_conn` → `MaxConn`)
|
||||
- Unexported fields
|
||||
- Fields where the Go name does not match the config key
|
||||
|
||||
```go
|
||||
// ✓ Good — explicit and immune to rename surprises
|
||||
type TLSConfig struct {
|
||||
CertFile string `mapstructure:"cert_file"`
|
||||
KeyFile string `mapstructure:"key_file"`
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
}
|
||||
|
||||
// ✗ Fragile — relies on case-folding; breaks when config key uses underscores
|
||||
type TLSConfig struct {
|
||||
CertFile string // viper key "certfile" or "CertFile", not "cert_file"
|
||||
KeyFile string
|
||||
Enabled bool
|
||||
}
|
||||
```
|
||||
|
||||
## UnmarshalKey — extracting a sub-tree
|
||||
|
||||
```go
|
||||
type DatabaseConfig struct {
|
||||
DSN string `mapstructure:"dsn"`
|
||||
MaxConn int `mapstructure:"max_conn"`
|
||||
}
|
||||
|
||||
var dbCfg DatabaseConfig
|
||||
if err := viper.UnmarshalKey("database", &dbCfg); err != nil {
|
||||
return fmt.Errorf("decoding database config: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
Prefer `UnmarshalKey` over `viper.Sub` + `Unmarshal` — fewer nil checks and less boilerplate.
|
||||
|
||||
## time.Duration
|
||||
|
||||
Viper's `GetDuration` parses duration strings (`"1h30m"`, `"500ms"`) from config files and env vars. When using `Unmarshal`, mapstructure does not know how to decode a duration string into `time.Duration` by default.
|
||||
|
||||
Register a decode hook:
|
||||
|
||||
```go
|
||||
import "github.com/mitchellh/mapstructure"
|
||||
|
||||
var cfg Config
|
||||
err := viper.Unmarshal(&cfg, func(dc *mapstructure.DecoderConfig) {
|
||||
dc.DecodeHook = mapstructure.ComposeDecodeHookFunc(
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
mapstructure.StringToSliceHookFunc(","),
|
||||
dc.DecodeHook,
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
`StringToTimeDurationHookFunc` handles `"1h30m"` → `time.Duration`. `StringToSliceHookFunc(",")` handles `"a,b,c"` → `[]string{"a", "b", "c"}`.
|
||||
|
||||
## net.IP and custom types
|
||||
|
||||
```go
|
||||
import "github.com/mitchellh/mapstructure"
|
||||
|
||||
func stringToIPHookFunc() mapstructure.DecodeHookFunc {
|
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
|
||||
if f.Kind() != reflect.String || t != reflect.TypeOf(net.IP{}) {
|
||||
return data, nil
|
||||
}
|
||||
ip := net.ParseIP(data.(string))
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("invalid IP address: %s", data)
|
||||
}
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Squash for embedded structs
|
||||
|
||||
```go
|
||||
type BaseConfig struct {
|
||||
LogLevel string `mapstructure:"log_level"`
|
||||
Debug bool `mapstructure:"debug"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
BaseConfig `mapstructure:",squash"` // merge BaseConfig fields at this level
|
||||
Port int `mapstructure:"port"`
|
||||
}
|
||||
```
|
||||
|
||||
Without `,squash`, the base config must be nested under a `baseconfig` key in the config file.
|
||||
|
||||
## Remain for unknown keys
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Port int `mapstructure:"port"`
|
||||
Remain map[string]interface{} `mapstructure:",remain"`
|
||||
}
|
||||
```
|
||||
|
||||
Extra keys from the config file are collected in `Remain` instead of being silently dropped. Useful for forward compatibility.
|
||||
|
||||
## Weak decoding
|
||||
|
||||
Enable weak type decoding (e.g., string `"true"` → bool `true`) when working with env vars that are always strings:
|
||||
|
||||
```go
|
||||
var cfg Config
|
||||
err := viper.Unmarshal(&cfg, func(dc *mapstructure.DecoderConfig) {
|
||||
dc.WeaklyTypedInput = true
|
||||
})
|
||||
```
|
||||
|
||||
Use with caution — weak decoding can hide bugs where a wrong value type is silently converted.
|
||||
Reference in New Issue
Block a user