mirror of
https://github.com/spf13/viper
synced 2024-12-23 03:57:01 +00:00
[110] Default Values Specify Type
This patch adds a feature, if enabled, will infer a value's type from its default value no matter from where else the value is set. This is particularly important when working with environment variables. For example: package main import ( "fmt" "os" "github.com/spf13/viper" ) func print(name string, val interface{}) { fmt.Printf("%-15[1]s%-15[2]T%[2]v\n", name, val) } func main() { viper.BindEnv("mykey", "MYPREFIX_MYKEY") viper.SetDefault("mykey", []string{}) os.Setenv("MYPREFIX_MYKEY", "a b c") v1 := viper.GetStringSlice("mykey") v2 := viper.Get("mykey") print("v1", v1) print("v2", v2) } When this program is executed the following is emitted: [0]akutz@pax:ex$ ./ex1 v1 []string [a b c] v2 string a b c [0]akutz@pax:ex$ You may wonder, why is this important? Just use the GetStringSlice function. Well, it *becomes* important when dealing with marshaling. If we update the above program to this: package main import ( "fmt" "os" "github.com/spf13/viper" ) type Data struct { MyKey []string } func print(name string, val interface{}) { fmt.Printf("%-15[1]s%-15[2]T%[2]v\n", name, val) } func main() { viper.BindEnv("mykey", "MYPREFIX_MYKEY") viper.SetDefault("mykey", []string{}) os.Setenv("MYPREFIX_MYKEY", "a b c") v1 := viper.GetStringSlice("mykey") v2 := viper.Get("mykey") print("v1", v1) print("v2", v2) d := &Data{} viper.Marshal(d) print("d.MyKey", d.MyKey) } Now we can see the issue when we execute the updated program: [0]akutz@pax:ex$ ./ex2 v1 []string [a b c] v2 string a b c d.MyKey []string [] [0]akutz@pax:ex$ The marshalled data structure's field MyKey is empty when in fact it should have a string slice equal to, in value, []string {"a", "b", "c"}. The problem is that viper's Marshal function calls AllSettings which ultimately uses the Get function. The Get function does try to infer the value's type, but it does so using the type of the value retrieved using this logic: Get has the behavior of returning the value associated with the first place from where it is set. Viper will check in the following order: * override * flag * env * config file * key/value store * default While the above order is the one we want when retrieving the values, this patch enables users to decide if it's the order they want to be used when inferring a value's type. To that end the function SetTypeByDefaultValue is introduced. When SetTypeByDefaultValue(true) is called, a call to the Get function will now first check a key's default value, if set, when inferring a value's type. This is demonstrated using a modified version of the same program above: package main import ( "fmt" "os" "github.com/spf13/viper" ) type Data struct { MyKey []string } func print(name string, val interface{}) { fmt.Printf("%-15[1]s%-15[2]T%[2]v\n", name, val) } func main() { viper.BindEnv("mykey", "MYPREFIX_MYKEY") viper.SetDefault("mykey", []string{}) os.Setenv("MYPREFIX_MYKEY", "a b c") v1 := viper.GetStringSlice("mykey") v2 := viper.Get("mykey") print("v1", v1) print("v2", v2) d1 := &Data{} viper.Marshal(d1) print("d1.MyKey", d1.MyKey) viper.SetTypeByDefaultValue(true) d2 := &Data{} viper.Marshal(d2) print("d2.MyKey", d2.MyKey) } Now the following is emitted: [0]akutz@pax:ex$ ./ex3 v1 []string [a b c] v2 string a b c d1.MyKey []string [] d2.MyKey []string [a b c] [0]akutz@pax:ex$
This commit is contained in:
parent
3c0ff861e3
commit
7a381db820
1 changed files with 44 additions and 10 deletions
40
viper.go
40
viper.go
|
@ -150,6 +150,7 @@ type Viper struct {
|
||||||
pflags map[string]*pflag.Flag
|
pflags map[string]*pflag.Flag
|
||||||
env map[string]string
|
env map[string]string
|
||||||
aliases map[string]string
|
aliases map[string]string
|
||||||
|
typeByDefValue bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns an initialized Viper instance.
|
// Returns an initialized Viper instance.
|
||||||
|
@ -164,6 +165,7 @@ func New() *Viper {
|
||||||
v.pflags = make(map[string]*pflag.Flag)
|
v.pflags = make(map[string]*pflag.Flag)
|
||||||
v.env = make(map[string]string)
|
v.env = make(map[string]string)
|
||||||
v.aliases = make(map[string]string)
|
v.aliases = make(map[string]string)
|
||||||
|
v.typeByDefValue = false
|
||||||
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
@ -368,6 +370,25 @@ func (v *Viper) searchMap(source map[string]interface{}, path []string) interfac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTypeByDefaultValue enables or disables the inference of a key value's
|
||||||
|
// type when the Get function is used based upon a key's default value as
|
||||||
|
// opposed to the value returned based on the normal fetch logic.
|
||||||
|
//
|
||||||
|
// For example, if a key has a default value of []string{} and the same key
|
||||||
|
// is set via an environment variable to "a b c", a call to the Get function
|
||||||
|
// would return a string slice for the key if the key's type is inferred by
|
||||||
|
// the default value and the Get function would return:
|
||||||
|
//
|
||||||
|
// []string {"a", "b", "c"}
|
||||||
|
//
|
||||||
|
// Otherwise the Get function would return:
|
||||||
|
//
|
||||||
|
// "a b c"
|
||||||
|
func SetTypeByDefaultValue(enable bool) { v.SetTypeByDefaultValue(enable) }
|
||||||
|
func (v *Viper) SetTypeByDefaultValue(enable bool) {
|
||||||
|
v.typeByDefValue = enable
|
||||||
|
}
|
||||||
|
|
||||||
// Viper is essentially repository for configurations
|
// Viper is essentially repository for configurations
|
||||||
// Get can retrieve any value given the key to use
|
// Get can retrieve any value given the key to use
|
||||||
// Get has the behavior of returning the value associated with the first
|
// Get has the behavior of returning the value associated with the first
|
||||||
|
@ -379,7 +400,8 @@ func Get(key string) interface{} { return v.Get(key) }
|
||||||
func (v *Viper) Get(key string) interface{} {
|
func (v *Viper) Get(key string) interface{} {
|
||||||
path := strings.Split(key, v.keyDelim)
|
path := strings.Split(key, v.keyDelim)
|
||||||
|
|
||||||
val := v.find(strings.ToLower(key))
|
lcaseKey := strings.ToLower(key)
|
||||||
|
val := v.find(lcaseKey)
|
||||||
|
|
||||||
if val == nil {
|
if val == nil {
|
||||||
source := v.find(path[0])
|
source := v.find(path[0])
|
||||||
|
@ -392,7 +414,19 @@ func (v *Viper) Get(key string) interface{} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch val.(type) {
|
var valType interface{}
|
||||||
|
if !v.typeByDefValue {
|
||||||
|
valType = val
|
||||||
|
} else {
|
||||||
|
defVal, defExists := v.defaults[lcaseKey]
|
||||||
|
if defExists {
|
||||||
|
valType = defVal
|
||||||
|
} else {
|
||||||
|
valType = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch valType.(type) {
|
||||||
case bool:
|
case bool:
|
||||||
return cast.ToBool(val)
|
return cast.ToBool(val)
|
||||||
case string:
|
case string:
|
||||||
|
@ -406,7 +440,7 @@ func (v *Viper) Get(key string) interface{} {
|
||||||
case time.Duration:
|
case time.Duration:
|
||||||
return cast.ToDuration(val)
|
return cast.ToDuration(val)
|
||||||
case []string:
|
case []string:
|
||||||
return val
|
return cast.ToStringSlice(val)
|
||||||
}
|
}
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue