Viper: FR: Unmarshal use specific decoder according to configType

Created on 27 Dec 2018  路  5Comments  路  Source: spf13/viper

Now, viper.Unmarshal use mapstructure.Decoder as the decoder.
So I need to change my struct tag to mapstructure, although my config file is YAML, and I set ConfigType to YAML too. This is too strange.

Most helpful comment

viper use mapstructure and it can be config by mapstructure.DecoderConfig, viper also export this option and you can set tag name or wharever your like like this:

viper.Unmarshal(rawVal, func(config *mapstructure.DecoderConfig) {
    config.TagName = "yaml"
    // do anything your like
})

All 5 comments

for example, #385

viper use mapstructure and it can be config by mapstructure.DecoderConfig, viper also export this option and you can set tag name or wharever your like like this:

viper.Unmarshal(rawVal, func(config *mapstructure.DecoderConfig) {
    config.TagName = "yaml"
    // do anything your like
})

viper use mapstructure and it can be config by mapstructure.DecoderConfig, viper also export this option and you can set tag name or wharever your like like this:

viper.Unmarshal(rawVal, func(config *mapstructure.DecoderConfig) {
    config.TagName = "yaml"
    // do anything your like
})

Thanks a lot. @yore-new

But viper ,not user, does this maybe better, I think. TagName correspond with ConfigType as default. User can custom this by themselves

package main

import (
    "bytes"
    "fmt"
    "time"

    yaml "gopkg.in/yaml.v2"

    "github.com/mitchellh/mapstructure"
    "github.com/spf13/viper"
)

type CustomType time.Time

func (ct *CustomType) UnmarshalYAML(unmarshal func(interface{}) error) error {
    var raw string
    if err := unmarshal(&raw); err != nil {
        return err
    }

    if t, err := time.Parse("2006.01.02", raw); err != nil {
        return err
    } else {
        *ct = CustomType(t)
    }
    return nil
}

var confStr = `---
custom: 2018.12.31  
`

type SS struct {
    Custom CustomType
}

func main() {
    confBuf := &bytes.Buffer{}
    confBuf.WriteString(confStr)

    v := viper.New()
    v.SetConfigType("yaml")
    _ = v.ReadConfig(confBuf)

    fmt.Printf("raw: %v\n", v.AllSettings())

    var s SS

    v.Unmarshal(&s, func(config *mapstructure.DecoderConfig) {
        config.TagName = "yaml"
    })
    fmt.Printf("unmarshal by viper: %v\n", s)

    tmp, _ := yaml.Marshal(v.AllSettings())
    yaml.Unmarshal(tmp, &s)
    fmt.Printf("unmarshal by yaml: %v\n", s)
}

And custom type does not work.

When viper loads a configuration file (eg. a YAML file) it normalizes and unmarshals the data into a map[string]interface{}. From that point the configuration has nothing to do with yaml anymore. Viper does this so that configuration from various sources (defaults, environment variables, flags) can all be "merged" into a single structure.

When you call Unmarshal on viper, it simply maps this data to your structure. Thus any yaml tags and yaml unmarshaling are out of scope at this point and this is why mapstructure is a better fit, but you can rename it to something else, like config.

If you want to do manual unmarshaling (like in the above example), you can use decode hooks (explained in the mapstructure documentation).

I would advise against mixing yaml and viper unmarshaling in a single struct as it can easily lead to confusions.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kubaugustyn picture kubaugustyn  路  3Comments

lllama picture lllama  路  3Comments

TWinsnes picture TWinsnes  路  3Comments

xh3b4sd picture xh3b4sd  路  6Comments

ankur22 picture ankur22  路  5Comments