Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error parsing time.Time #496

Closed
noandrea opened this issue May 8, 2018 · 8 comments
Closed

Error parsing time.Time #496

noandrea opened this issue May 8, 2018 · 8 comments

Comments

@noandrea
Copy link

noandrea commented May 8, 2018

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
@ethanmick
Copy link

Is there a workaround for this?

@noandrea
Copy link
Author

noandrea commented Oct 15, 2018

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

@lachlanmunro
Copy link

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")
}

@leventov
Copy link

Thanks @lachlanmunro. FYI a more comprehensive time decoder function is given here: mitchellh/mapstructure#159 (comment)

@noandrea
Copy link
Author

I guess this can be closed then

@invernizzie
Copy link

Just in case someone has a quick answer before I dive into the code, why does time.Duration work out of the box while time.Time doesn't?

Even though time.Duration is backed by a native type, it still needs special handling since strings are (presumably) parsed using the native functions in the time package.

@sagikazarmark
Copy link
Collaborator

Parsing time.Duration is obvious, while parsing time.Time also needs a(t least a) format.

@zostay
Copy link

zostay commented Jun 11, 2022

A simpler work-around for certain cases may be this:

timeVal := viper.GetTime("time_val")
viper.Set("time_val", timeVal)
viper.Unmarshal(&structVal)

This pre-converts it to a time value and you'll get all of viper's built-in time format detection. It might be a pain to do on a complicated configuration file, but for something simple, this does the trick for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants