I'm trying to override AllInputChannels
with an envvar. The main config is in config.yaml
which is picked up fine.
Running the below code viper.Unmarshal()
throws an error:
'AllInputChannels' expected a map, got 'string'
How should I present the value in the envvar so it's recognised?
I've removed error checking code below for clarity, except where the error appears.
type ChannelMap struct {
Team string `mapstructure:"Team"`
Channel string `mapstructure:"Channel"`
}
type AllInputChannels struct {
Production []ChannelMap `mapstructure:"Production"`
Staging []ChannelMap `mapstructure:"Staging"`
}
type Config struct {
AllInputChannels AllInputChannels `mapstructure:"AllInputChannels"`
}
func NewConfig() Config { return Config{} }
func Build() Config {
conf := NewConfig()
_ = os.Setenv("ALLINPUTCHANNELS", `{"staging":[{"team":"Foo","channel":"DEF456"}]}`)
viper.AddConfigPath(".") // Local builds.
viper.SetConfigName("config")
viper.SetConfigType("yaml")
_ = viper.BindEnv("AllInputChannels")
_ = viper.ReadInConfig()
err := viper.Unmarshal(&conf)
if err != nil {
fmt.Printf("unable to decode config, %v", err)
}
return conf, nil
}
func main() {
_ = Build()
}
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/mitchellh/mapstructure"
"github.com/spf13/viper"
)
type ChannelMap struct {
Team string `mapstructure:"team"`
Channel string `mapstructure:"channel"`
}
type AllInputChannels struct {
Production []ChannelMap `mapstructure:"production"`
Staging []ChannelMap `mapstructure:"staging"`
}
type Config struct {
AllInputChannels AllInputChannels `mapstructure:"AllInputChannels"`
}
func NewConfig() Config { return Config{} }
func Build() Config {
conf := NewConfig()
_ = os.Setenv("ALLINPUTCHANNELS", `{"staging":[{"team":"Foo","channel":"DEF456"}]}`)
viper.AutomaticEnv()
viper.AddConfigPath(".") // Local builds.
viper.SetConfigName("config")
viper.SetConfigType("yaml")
// Load the environment variable and unmarshal the JSON value into the struct
if envValue := viper.GetString("ALLINPUTCHANNELS"); envValue != "" {
var allInputChannels AllInputChannels
err := json.Unmarshal([]byte(envValue), &allInputChannels)
if err != nil {
fmt.Printf("Error unmarshaling JSON from environment variable: %v\n", err)
return conf
}
conf.AllInputChannels = allInputChannels
}
_ = viper.ReadInConfig()
err := viper.Unmarshal(&conf, func(c *mapstructure.DecoderConfig) {
c.TagName = "mapstructure"
})
if err != nil {
fmt.Printf("unable to decode config, %v", err)
}
return conf
}
func main() {
conf := Build()
fmt.Printf("Config: %+v\n", conf)
}
changes made :-
mapstructure
tags are case-sensitive and must match the keys in the JSON data exactly.os.Setenv("ALLINPUTCHANNELS", ...)
sets the environment variable correctly.viper.AutomaticEnv()
ensures Viper reads environment variables automatically.json.Unmarshal
into the AllInputChannels
struct.AllInputChannels
field in the conf struct is then set to the parsed value.conf
instead conf,nil
where only config
required as return value