-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to get a array config value from env? #339
Comments
This is a good question. So we can retrieve this list like this: 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:
Overriding this through env:
@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? |
Is it possible? |
Definitely, just spent quite some time going through the code to find how to pass stringslices through ENV... |
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 |
For Example- variable will look like- If anyone can help us as this is imp and urgent project delivery is pending. |
The solution does not seem to work with 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)
}
}
|
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:
Then in my code that actually loads the configuration:
edit: as we've got lots of unit tests, in the end my helper function looks like this, with some additional checks:
|
@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: |
@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. |
…gular admin (#4228) ## Description This allows working around an issue with Viper where it doesn't properly unmarshal GetIntSlice from environment variables. Seems like this is a known issue and is quite old. I'm assuming there isn't plans to fix it on the Viper side. spf13/viper#339 ## Tests I'll be brutally honest, I don't know how to run Go, but this seemed like a pretty quick low hanging fix.
Maps are currently not supported in environment variables. See also spf13/viper#339
Maps are currently not supported in environment variables. See also spf13/viper#339
Maps are currently not supported in environment variables. See also spf13/viper#339
Maps are currently not supported in environment variables. See also spf13/viper#339
e.g I want get the hobbies(in README) value from env.
The text was updated successfully, but these errors were encountered: