Open wangxufire opened 7 years ago
This is a good question. So we can retrieve this list like this: viper.GetStringSlice("hobbies")
.
Example Code:
package main
import (
"fmt"
"github.com/spf13/viper"
"bytes"
"strings"
)
var yamlExample = []byte(`
name: steve
hobbies:
- skateboarding
- snowboarding
`)
func main(){
viper.SetConfigType("yaml")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
viper.ReadConfig(bytes.NewBuffer(yamlExample))
t := viper.GetStringSlice("hobbies")
fmt.Printf( "Type: %T, Size: %d \n", t, len(t) )
for i, v := range t {
fmt.Printf("Index: %d, Value: %v\n", i, v )
}
}
When I run this without setting an environment variable override I get:
$ go run main.go
Type: []string, Size: 2
Index: 0, Value: skateboarding
Index: 1, Value: snowboarding
Overriding this through env:
$ HOBBIES="devops go docker ansible" go run main.go
Type: []string, Size: 4
Index: 0, Value: devops
Index: 1, Value: go
Index: 2, Value: docker
Index: 3, Value: ansible
@spf13 perhaps we should include this in the main README.md? Also what if we have a list of maps, how can we override that?
Also what if we have a list of maps, how can we override that?
Is it possible?
@spf13 perhaps we should include this in the main README.md? Also what if we have a list of maps, how can we override that?
Definitely, just spent quite some time going through the code to find how to pass stringslices through ENV... Also some way to support spaces in the slices would be nice
Also what if we have a list of maps, how can we override that?
Has anyone figured this out?
Also what if we have a list of maps, how can we override that?
Has anyone figured this out?
going back to code to find that as I needed as well (but haven't tested yet) https://github.com/spf13/cast/blob/master/caste.go#L876
So if we're using GetStringMap
it ends there where, if value is string, it will be parsed as JSON... 👍 for doc updates again...
Also what if we have a list of maps, how can we override that?
Has anyone figured this out?
going back to code to find that as I needed as well (but haven't tested yet) https://github.com/spf13/cast/blob/master/caste.go#L876
So if we're using
GetStringMap
it ends there where, if value is string, it will be parsed as JSON... 👍 for doc updates again... Hey we tried to use 'GetStringMap' an dalso 'GetStringMapStringSlice' for the list of maps variables but not able to get any luck. Please help us by giving some example that how can we fetch the value of list of map variable.
For Example- variable will look like- backend-pool-ip-addresses = [{"IpAddress" = "10.0.0.5"}, {"IpAddress" = "10.0.0.6"}] which is reading like- backend-pool-ip-addresses:[map[IpAddress:10.0.0.5] map[IpAddress:10.0.0.6]]
If anyone can help us as this is imp and urgent project delivery is pending.
The solution does not seem to work with GetIntSlice
:
package main
import (
"bytes"
"fmt"
"github.com/spf13/viper"
"strings"
)
var yamlExample = []byte(`
name: steve
hobbies:
- 10
- 20
`)
func main() {
viper.SetConfigType("yaml")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
viper.ReadConfig(bytes.NewBuffer(yamlExample))
t := viper.GetIntSlice("hobbies") // <----------------------
fmt.Printf("Type: %T, Size: %d \n", t, len(t))
for i, v := range t {
fmt.Printf("Index: %d, Value: %v\n", i, v)
}
}
go run .
Type: []int, Size: 2
Index: 0, Value: 10
Index: 1, Value: 20
HOBBIES="100 200" go run .
Type: []int, Size: 0
I met the situation that need to parse the env variables which type is a list of map to a structure.
So after a try, i wrote codes below to try to solve.
I parodied the function, mapstructure.StringToSliceHookFunc.
Maybe this is a not good way, but it may be for reference.
package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"reflect"
"strings"
"github.com/mitchellh/mapstructure"
"github.com/spf13/viper"
)
var yamlExample = []byte(`
name: steve
hobbies:
- id: a
name: aaa
- id: b
name: bbb
`)
var envExample = `[
{"id":"a","name":"no_aaa"}
]`
type Conf struct {
Name string `json:"name"`
Hobbies []struct {
Id string `json:"id"`
Name string `json:"name"`
} `json:"hobbies"`
}
func main() {
os.Setenv("NAME", "who")
os.Setenv("HOBBIES", envExample)
viper.SetConfigType("yaml")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
viper.ReadConfig(bytes.NewBuffer(yamlExample))
var conf *Conf
err := viper.Unmarshal(&conf, func(dc *mapstructure.DecoderConfig) {
dc.DecodeHook = mapstructure.ComposeDecodeHookFunc(
StringToStructHookFunc(),
StringToSliceWithBracketHookFunc(),
dc.DecodeHook)
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(fmt.Sprintf("%+v", conf)) // &{Name:who Hobbies:[{Id:a Name:no_aaa}]}
}
func StringToSliceWithBracketHookFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
if f != reflect.String || t != reflect.Slice {
return data, nil
}
raw := data.(string)
if raw == "" {
return []string{}, nil
}
var slice []json.RawMessage
err := json.Unmarshal([]byte(raw), &slice)
if err != nil {
return data, nil
}
var strSlice []string
for _, v := range slice {
strSlice = append(strSlice, string(v))
}
return strSlice, nil
}
}
func StringToStructHookFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{},
) (interface{}, error) {
if f.Kind() != reflect.String ||
(t.Kind() != reflect.Struct && !(t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Struct)) {
return data, nil
}
raw := data.(string)
var val reflect.Value
// Struct or the pointer to a struct
if t.Kind() == reflect.Struct {
val = reflect.New(t)
} else {
val = reflect.New(t.Elem())
}
if raw == "" {
return val, nil
}
err := json.Unmarshal([]byte(raw), val.Interface())
if err != nil {
return data, nil
}
return val.Interface(), nil
}
}
Has anyone figured out how to pass a list of maps through an environment variable?
My solution, if it helps:
func handleBrokersConfigEnvVar() {
// if BROKERS if found in env, handle it
const key = "brokers"
// Get the value as string
brokers := viper.GetString(key)
// unmarshall it into a []map[string]interface{}, which is what Viper knows how to work with
resultArr := make([]map[string]interface{}, len(brokers))
err := json.Unmarshal([]byte(brokers), &resultArr)
if err != nil {
log.Error().Msgf("Error decoding content of CCX_NOTIFICATION_WRITER__BROKERS env var: %v\n", err)
}
// Here's thhe trick: give the []map[string]interface{} to Viper
viper.Set(key, resultArr)
}
Then in my code that actually loads the configuration:
// override configuration from env if there's variable in env
const envPrefix = "THE_ENV_PREFIX"
viper.AutomaticEnv()
viper.SetEnvPrefix(envPrefix)
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "__"))
// call the helper function before unmarshalling to your struct
handleBrokersConfigEnvVar()
err = viper.Unmarshal(&configuration)
if err != nil {
return configuration, err
}
edit: as we've got lots of unit tests, in the end my helper function looks like this, with some additional checks:
func handleBrokersConfigEnvVar() {
// if BROKERS if found in env, handle it
const key = "brokers"
brokers := viper.GetString(key)
if brokers == "" {
log.Warn().Msg("nothing to do for the brokers env var")
// When you remove an environment variable, Viper might still have the previously
// set value cached in memory and doesn't automatically revert to the value from
// the configuration file. So this line is purely here so our unit tests pass
viper.Set(key, nil)
return
}
resultArr := make([]map[string]interface{}, len(brokers))
err := json.Unmarshal([]byte(brokers), &resultArr)
if err != nil {
log.Error().Msgf("Error decoding content of BROKERS env var: %v", err)
}
viper.Set(key, resultArr)
}
@comminutus with the solution I proposed, you simply pass a JSON array of objects as environment variable. In my case, it's Kafka brokers' configuration, so I add this env var: BROKERS='[{"address":"address1:port1","topic":"topic1"},{"address":"address2:port2","topic":"topic2"}]'
@epapbak unfortunately, I'm dependent upon a project which uses viper and can't modify the source. It'd be better to have first-order support for this in viper itself.
e.g I want get the hobbies(in README) value from env.