Search code examples
goviper-go

Using viper to read config from envfile


I don't really understand how viper works. This is my code:

configuration.go

var Config *Configuration

type ServerConfiguration struct {
    Port string
}

type Configuration struct {
    Server   ServerConfiguration
}

func Init() {
    var configuration *Configuration
    viper.SetConfigFile(".env")
    viper.AutomaticEnv()
    if err := viper.ReadInConfig(); err != nil {
        log.Fatalf("Error reading config file, %s", err)
    }

    err := viper.Unmarshal(&configuration)
    if err != nil {
        log.Fatalf("Unable to decode into struct, %v", err)
    }

    Config = configuration
}

func GetConfig() *Configuration {
    return Config
}

.env SERVER_PORT=:4747

The problem is that Unmarshal does not work When I use for example configuration.Server.Port it's empty


Solution

  • spf13/viper predominantly uses mapstructure package to convert between one native Go type to another i.e. when un-marshaling. The package internally uses map[string]interface{} type to store your config (see viper.go - L1327). After that depending on the config type (your case being env), viper calls the right parsing package to store your config values. For envfile type, it uses subosito/gotenv to put in the above said map type (see viper.go - L1501)

    The crux of your problem is how to make viper unmarshal this config in a map to a struct of your choice. This is where the mapstructure package comes in, to unmarshal the map into a nested structure of you have defined. At this point you are left with two options

    1. Unmarshal the config as a map[string]interface{} type, and later put in the appropriate structure using mapstructure
    2. Use the DecodeHookFunc as a 2nd argument to your method to unmarshal your config (See viper.go - L904)

    For the sake of simplicity reasons you can do one, which according to a trivial example I've reproduced with your example can be done below

    package main
    
    import (
        "fmt"
        "github.com/mitchellh/mapstructure"
        "github.com/spf13/viper"
    )
    
    type ServerConfiguration struct {
        Port string `mapstructure:"server_port"`
    }
    
    type Configuration struct {
        Server ServerConfiguration `mapstructure:",squash"`
    }
    
    func main() {
        var result map[string]interface{}
        var config Configuration
        viper.SetConfigFile(".env")
        if err := viper.ReadInConfig(); err != nil {
            fmt.Printf("Error reading config file, %s", err)
        }
    
        err := viper.Unmarshal(&result)
        if err != nil {
            fmt.Printf("Unable to decode into map, %v", err)
        }
    
        decErr := mapstructure.Decode(result, &config)
    
        if decErr != nil {
            fmt.Println("error decoding")
        }
    
        fmt.Printf("config:%+v\n", config)
    }
    

    You can make this working example customized depending on your actual use case. More information about the mapstructure squash tags for embedded structure can be found here