Search code examples
goiniviper-go

Unmarshaling INI file using Viper Go is not working as expected


I am using viper for app configuration.

Code sample is below.

package main

import "github.com/spf13/viper"

type Config struct {
    WelcomeMessage string `mapstructure:"message"`
}

func main() {

    viper.SetConfigName("config")
    viper.SetConfigType("ini")
    viper.AddConfigPath(".")
    err := viper.ReadInConfig() // Find and read the config file
    if err != nil {             // Handle errors reading the config file
        //handle error
    }

    // var result map[string]interface{}
    config := Config{}

    err = viper.Unmarshal(&config)
    if err != nil {
        //handle error
    }
    ...

}

INI file is simple with single config.

message=Welcome!

I can read configuration value using viper.Get() if I use default.message as key.

For some reason, Unmarshal is not working, meaning that there config variable is struct without value set in WelcomeMessage field.

What am I doing wrong?

Thanks.


Solution

  • The https://github.com/spf13/viper project is woefully under documented. Looking at the source I figured out what is needed to make your code work. The key insight is that the key you are trying to unmarshal is implicitly in a section named "default". So your structure has to put the corresponding variable inside a structure named "Default".

    I started by creating a file named config.ini with this content:

    message=Welcome!
    hello = goodbye
    

    I then put the following code in a file named x.go:

    package main
    
    import (
            "fmt"
    
            "github.com/spf13/viper"
    )
    
    type Config struct {
            Default struct {
                    WelcomeMessage string `mapstructure:"message"`
                    Hello          string
                    Undefined      string
            }
    }
    
    func main() {
    
            viper.SetConfigName("config")
            viper.SetConfigType("ini")
            viper.AddConfigPath(".")
            err := viper.ReadInConfig() // Find and read the config file
            if err != nil {             // Handle errors reading the config file
                    panic(err)
            }
    
            // var result map[string]interface{}
            config := Config{}
    
            err = viper.Unmarshal(&config)
            if err != nil {
                    panic(err)
            }
    
            fmt.Printf("%#v\n", config)
    }
    

    Executing "go run x.go" produces this output (which matches what you expect, modulo the need for a nested struct):

    main.Config{Default:struct { WelcomeMessage string "mapstructure:\"message\""; Hello string; Undefined string }{WelcomeMessage:"Welcome!", Hello:"goodbye", Undefined:""}}
    

    P.S., Having gone to the trouble of solving this question, and looking at the Viper project source code (including its unit tests, API and documentation) I do not recommend using that project.

    P.P.S., The INI file format is like the CSV format. Both were created by Microsoft and were horribly underspecified at the time of their introduction. Resulting in many ambiguities and incompatible implementations. Both formats should be avoided.