mirror of https://github.com/spf13/viper.git
Support customising mapstructure.DecoderConfig for Unmarshal
* Added a new `DecoderConfigOption` type allowing the user to write custom functions that can override the default mapstructure.DecoderConfig settings * Added a new `DecodeHook` function which returns a `DecoderConfigOption`. This allows the user to easily set their own Decode hooks when Unmarshaling * Updated Unmarshal, UnmarshalKey and defaultDecoderConfig to support variadic trailing `DecoderConfigOption` functions to allow for customisation of the default mapstructure.DecoderConfig * Added a test case with example usage
This commit is contained in:
parent
d493c32b69
commit
907c19d40d
41
viper.go
41
viper.go
|
@ -113,6 +113,23 @@ func (fnfe ConfigFileNotFoundError) Error() string {
|
||||||
return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations)
|
return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A DecoderConfigOption can be passed to viper.Unmarshal to configure
|
||||||
|
// mapstructure.DecoderConfig options
|
||||||
|
type DecoderConfigOption func(*mapstructure.DecoderConfig)
|
||||||
|
|
||||||
|
// DecodeHook returns a DecoderConfigOption which overrides the default
|
||||||
|
// DecoderConfig.DecodeHook value, the default is:
|
||||||
|
//
|
||||||
|
// mapstructure.ComposeDecodeHookFunc(
|
||||||
|
// mapstructure.StringToTimeDurationHookFunc(),
|
||||||
|
// mapstructure.StringToSliceHookFunc(","),
|
||||||
|
// )
|
||||||
|
func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOption {
|
||||||
|
return func(c *mapstructure.DecoderConfig) {
|
||||||
|
c.DecodeHook = hook
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Viper is a prioritized configuration registry. It
|
// Viper is a prioritized configuration registry. It
|
||||||
// maintains a set of configuration sources, fetches
|
// maintains a set of configuration sources, fetches
|
||||||
// values to populate those, and provides them according
|
// values to populate those, and provides them according
|
||||||
|
@ -745,9 +762,11 @@ func (v *Viper) GetSizeInBytes(key string) uint {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalKey takes a single key and unmarshals it into a Struct.
|
// UnmarshalKey takes a single key and unmarshals it into a Struct.
|
||||||
func UnmarshalKey(key string, rawVal interface{}) error { return v.UnmarshalKey(key, rawVal) }
|
func UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error {
|
||||||
func (v *Viper) UnmarshalKey(key string, rawVal interface{}) error {
|
return v.UnmarshalKey(key, rawVal, opts...)
|
||||||
err := decode(v.Get(key), defaultDecoderConfig(rawVal))
|
}
|
||||||
|
func (v *Viper) UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error {
|
||||||
|
err := decode(v.Get(key), defaultDecoderConfig(rawVal, opts...))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -760,9 +779,11 @@ func (v *Viper) UnmarshalKey(key string, rawVal interface{}) error {
|
||||||
|
|
||||||
// Unmarshal unmarshals the config into a Struct. Make sure that the tags
|
// Unmarshal unmarshals the config into a Struct. Make sure that the tags
|
||||||
// on the fields of the structure are properly set.
|
// on the fields of the structure are properly set.
|
||||||
func Unmarshal(rawVal interface{}) error { return v.Unmarshal(rawVal) }
|
func Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error {
|
||||||
func (v *Viper) Unmarshal(rawVal interface{}) error {
|
return v.Unmarshal(rawVal, opts...)
|
||||||
err := decode(v.AllSettings(), defaultDecoderConfig(rawVal))
|
}
|
||||||
|
func (v *Viper) Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error {
|
||||||
|
err := decode(v.AllSettings(), defaultDecoderConfig(rawVal, opts...))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -775,8 +796,8 @@ func (v *Viper) Unmarshal(rawVal interface{}) error {
|
||||||
|
|
||||||
// defaultDecoderConfig returns default mapsstructure.DecoderConfig with suppot
|
// defaultDecoderConfig returns default mapsstructure.DecoderConfig with suppot
|
||||||
// of time.Duration values & string slices
|
// of time.Duration values & string slices
|
||||||
func defaultDecoderConfig(output interface{}) *mapstructure.DecoderConfig {
|
func defaultDecoderConfig(output interface{}, opts ...DecoderConfigOption) *mapstructure.DecoderConfig {
|
||||||
return &mapstructure.DecoderConfig{
|
c := &mapstructure.DecoderConfig{
|
||||||
Metadata: nil,
|
Metadata: nil,
|
||||||
Result: output,
|
Result: output,
|
||||||
WeaklyTypedInput: true,
|
WeaklyTypedInput: true,
|
||||||
|
@ -785,6 +806,10 @@ func defaultDecoderConfig(output interface{}) *mapstructure.DecoderConfig {
|
||||||
mapstructure.StringToSliceHookFunc(","),
|
mapstructure.StringToSliceHookFunc(","),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// A wrapper around mapstructure.Decode that mimics the WeakDecode functionality
|
// A wrapper around mapstructure.Decode that mimics the WeakDecode functionality
|
||||||
|
|
|
@ -7,6 +7,7 @@ package viper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -18,6 +19,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
|
|
||||||
|
@ -503,6 +505,42 @@ func TestUnmarshal(t *testing.T) {
|
||||||
assert.Equal(t, &config{Name: "Steve", Port: 1234, Duration: time.Second + time.Millisecond}, &C)
|
assert.Equal(t, &config{Name: "Steve", Port: 1234, Duration: time.Second + time.Millisecond}, &C)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalWithDecoderOptions(t *testing.T) {
|
||||||
|
Set("credentials", "{\"foo\":\"bar\"}")
|
||||||
|
|
||||||
|
opt := DecodeHook(mapstructure.ComposeDecodeHookFunc(
|
||||||
|
mapstructure.StringToTimeDurationHookFunc(),
|
||||||
|
mapstructure.StringToSliceHookFunc(","),
|
||||||
|
// Custom Decode Hook Function
|
||||||
|
func(rf reflect.Kind, rt reflect.Kind, data interface{}) (interface{}, error) {
|
||||||
|
if rf != reflect.String || rt != reflect.Map {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
m := map[string]string{}
|
||||||
|
raw := data.(string)
|
||||||
|
if raw == "" {
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
return m, json.Unmarshal([]byte(raw), &m)
|
||||||
|
},
|
||||||
|
))
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
Credentials map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
var C config
|
||||||
|
|
||||||
|
err := Unmarshal(&C, opt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to decode into struct, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, &config{
|
||||||
|
Credentials: map[string]string{"foo": "bar"},
|
||||||
|
}, &C)
|
||||||
|
}
|
||||||
|
|
||||||
func TestBindPFlags(t *testing.T) {
|
func TestBindPFlags(t *testing.T) {
|
||||||
v := New() // create independent Viper object
|
v := New() // create independent Viper object
|
||||||
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||||
|
|
Loading…
Reference in New Issue