Go 中的 time 类型 JSON 如何解析

golang time Unmarshal 必须包含时区吗?

地址:https://segmentfault.com/q/1010000020616117

标签:Go

详情描述

不包含时区貌似不会成功呢

我的回答

先引用下楼上的回答,如下:

必须包含,文档里面有https://golang.org/pkg/time/#Time.UnmarshalJSON,时间必须符合RFC 3339格式。

要自定义Unmarshal,需要实现json.Unmarshaler接口。查看 这里

楼上的回答说的很对,不知道题主问题解决了没有。我也测试了一下,随便把一些过程写在这里。

首先,要支持 JSON 的编解码,就必须要实现两个接口,分别是 json.Marshaler(组包) 和 json.Unmashaler(解包)。

两个接口的定义如下:

type Marshaler interface {
	MarshalJSON() ([]byte, error)
}

type Unmarshaler interface {
	UnmarshalJSON([]byte) error
}

第一种方案,为什么必须是 RFC 3339 格式。

time.Time 已经实现了这两个接口,可以进入到 time/time.go 找到源码实现。而之所以字段必须是 RFC 3999,因为 time.Time 实现的 UnmarshalJSON 方法就是按这个格式解析的。

time.Time 的 UnmarshalJSON 的源码实现如下:

func (t *Time) UnmarshalJSON(data []byte) error {
	// Ignore null, like in the main JSON package.
	if string(data) == "null" {
		return nil
	}
	// Fractional seconds are handled implicitly by Parse.
	var err error
	*t, err = Parse(`"`+RFC3339+`"`, string(data))
	return err
}

非常简单。

关于什么是 RFC 3999 格式,题主可以参考这篇文章,互联网上的日期和时间

假设,现在需要一个支持自定义格式的时间类型,Go 还是非常灵活的,基于 time.Time 重新定义即可,在这个基础上再实现 json.Unmarshaler 接口。

type CTime time.Time
// UnmarshalJSON is joson.Unmarshaler
func (ct *CustomTime) UnmarshalJSON(data []byte) error {
	// Ignore null, like in the main JSON package.
	if string(data) == "null" {
		return nil
	}
	// Fractional seconds are handled implicitly by Parse.
	var err error
	t, _ := time.ParseInLocation(`"2006-01-02T15:04:05"`, string(data), time.Local)
	*ct = CustomTime(t)
	return err
}

和之前的 time.Time 实现基本一样,只是解析的格式不一样了。这里就可以不包含时区了,因为已经固定时区为 time.Local,即按当前时区解析。

如果想做的更灵活点,可以用 for range 把每个时间格式测试一下,通过判断 Parse 返回的 err 确定是否解析成功。当然,这种方法不推荐。

这种方式的缺点是 fmt.Printf 打印的时候,时间不够直观,打印的底层的数据,可以自己试下就知道。当然,这应该是可以解决的。

还有另外一种方式,定义一个新的时间类型结构体,里面只有一个 time.Time 类型成员。

type CustomTime struct {
	Time time.Time
}

func (ct *CustomTime) UnmarshalJSON(data []byte) error {
	// Ignore null, like in the main JSON package.
	if string(data) == "null" {
		return nil
	}
	// Fractional seconds are handled implicitly by Parse.
	var err error
	ct.Time, err = time.ParseInLocation(`"2006-01-02T15:04:05"`, string(data), time.Local)
	return err
}

代码都差不多。这种方式再使用 fmt.Printf 打印,效果就好了很多。