Viper: Error parsing time.Time

Created on 8 May 2018  路  4Comments  路  Source: spf13/viper

go: 1.10
viper: 1.0.2

when a field to be parsed is time.Time viper fails with error expected a map, got 'string'

Test to reproduce (can be added in viper_test.go )

func TestParseNestedTime(t *testing.T) {
    type duration struct {
        Moment time.Time
    }

    type item struct {
        Name   string
        Moment time.Time
        Nested duration
    }

    config := `[[parent]]
    moment="2011-10-09"
    [parent.nested]
    moment="2009-08-07"
`
    initConfig("toml", config)

    var items []item
    err := v.UnmarshalKey("parent", &items)
    if err != nil {
        t.Fatalf("unable to decode into struct, %v", err)
    }

    var m time.Time
    assert.Equal(t, 1, len(items))
    m, _ = time.Parse("2016-01-02", "2011-10-09")
    assert.Equal(t, m, items[0].Moment)
    m, _ = time.Parse("2016-01-02", "2009-08-07")
    assert.Equal(t, m, items[0].Nested.Moment)
}

result:

--- FAIL: TestParseNestedTime (0.00s)
    viper_test.go:1193: unable to decode into struct, 2 error(s) decoding:

        * '[0].Moment' expected a map, got 'string'
        * '[0].Nested.Moment' expected a map, got 'string'
FAIL
FAIL    github.com/spf13/viper  0.033s

Most helpful comment

@ethanmick my workaround now is to define the field as string and then parse it into date "manually"

All 4 comments

Is there a workaround for this?

@ethanmick my workaround now is to define the field as string and then parse it into date "manually"

Bumped into this, things have moved on:

The unmarshal method signature isn't quite as presently decribed in the docs (it is viper.Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error) so with options config glued together with something like (fiddled to handle empty strings below) mapstructure.StringToTimeHookFunc you can configure the decode and get what you want.

err := viper.GetViper().Unmarshal(&config, func(m *mapstructure.DecoderConfig) {
    m.DecodeHook = mapstructure.ComposeDecodeHookFunc(
        func(
            f reflect.Type,
            t reflect.Type,
            data interface{}) (interface{}, error) {
            if f.Kind() != reflect.String {
                return data, nil
            }
            if t != reflect.TypeOf(time.Time{}) {
                return data, nil
            }

            asString := data.(string)
            if asString == "" {
                return time.Time{}, nil
            }

            // Convert it by parsing
            return time.Parse("2006-01-02", asString)
        },
        mapstructure.StringToTimeDurationHookFunc(),
        mapstructure.StringToSliceHookFunc(","),
    )
})
if err != nil {
    log.Fatal().Err(err).Msg("Failed to unmarshal config")
}

Thanks @lachlanmunro. FYI a more comprehensive time decoder function is given here: https://github.com/mitchellh/mapstructure/issues/159#issuecomment-482201507

Was this page helpful?
0 / 5 - 0 ratings