在Go语言中,我们可以很方便的使用标准库encoding/json进行结构体的(反)序列化,它会自动帮我们解析嵌套的structs,以及内部的string、int、map等基本类型,但对于某些特殊类型就不是那么好用了。
举例一个典型就是标准库time.Time,在结构体中使用该类型表达时间,对后续编程操作非常方便。
但是如果我们对它进行JSON编码,你会发现序列化后的时间格式并不是我们想要的,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package main import ( "encoding/json" "fmt" "time" ) type Demo struct { T time.Time } func main() { d := Demo{T: time.Now()} s, _ := json.Marshal(d) fmt.Println(string(s)) } |
输出如下:
1 |
{"T":"2020-09-09T18:00:49.18734+08:00"} |
我们当然希望时间格式编码为这样:
1 |
2020-09-09 18:11:34 |
这里就要首先搞明白json.Marshal方法是如何编码time.Time字段的,才能进一步解决自定义格式问题。
time.Time编码原理json.Marshal会反射传入的Demo对象,查看它里面的每个字段,然后遇到了T time.Time。
对于内置类型(例如int、string等),它会直接编码,但现在是time.Time自定义类型,它会做点额外的判定,说明如下:
1 2 3 4 5 6 7 8 9 |
// Marshal traverses the value v recursively. // If an encountered value implements the Marshaler interface // and is not a nil pointer, Marshal calls its MarshalJSON method // to produce JSON. If no MarshalJSON method is present but the // value implements encoding.TextMarshaler instead, Marshal calls // its MarshalText method and encodes the result as a JSON string. // The nil pointer exception is not strictly necessary // but mimics a similar, necessary exception in the behavior of // UnmarshalJSON. |
它会反射判定2个事实,首先是看一下time.Time有没有实现如下接口:
1 2 3 |
type Marshaler interface { MarshalJSON() ([]byte, error) } |
如果有的话,那么就会执行time.Time.MarshalJSON()得到自定义序列化后的字符串。
如果没有实现该接口,还会再判定有没有实现如下接口:
1 2 3 |
type TextMarshaler interface { MarshalText() (text []byte, err error) } |
作用一样,也是自定义序列化,返回一个字符串。
那么这俩接口有啥区别呢?不如看看time.Time类型的实现,它把两者都实现了,怎么用就是上层的问题了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// MarshalJSON implements the json.Marshaler interface. // The time is a quoted string in RFC 3339 format, with sub-second precision added if present. func (t Time) MarshalJSON() ([]byte, error) { if y := t.Year(); y < 0 || y >= 10000 { // RFC 3339 is clear that years are 4 digits exactly. // See golang.org/issue/4556#c15 for more discussion. return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]") } b := make([]byte, 0, len(RFC3339Nano)+2) b = append(b, '"') b = t.AppendFormat(b, RFC3339Nano) b = append(b, '"') return b, nil } // MarshalText implements the encoding.TextMarshaler interface. // The time is formatted in RFC 3339 format, with sub-second precision added if present. func (t Time) MarshalText() ([]byte, error) { if y := t.Year(); y < 0 || y >= 10000 { return nil, errors.New("Time.MarshalText: year outside of range [0,9999]") } b := make([]byte, 0, len(RFC3339Nano)) return t.AppendFormat(b, RFC3339Nano), nil } |
核心区别在于,MarshalJSON需要返回带引号的字符串:
1 |
return "\"2020-09-09T18:00:49.18734+08:00\"" |
可以想想encoding/json库会直接把返回值拼到最终JSON串上。
MarshalText则简单的多,直接返回不带引号的部分即可:
1 |
return “2020-09-09T18:00:49.18734+08:00” |
所以总结一下,如果我们自定义的类型想序列化成int这种数字,只能用MarshalJson来返回一个例如”5″这样的字符串(注意没有嵌套引号)。
改变time.Time序列化实现我们可以通过给time.Time定义一个类型别名,然后给新类型自定义MarshalText方法,同时还能兼顾到time.Time原生方法的调用,一举两得。
如果我们仅仅定义新类型,那么JSON库反射新类型并不能找到序列化接口的实现,因此结果是空的:
1 2 3 4 5 6 7 8 9 10 |
type MyTime time.Time type Demo struct { T MyTime } func main() { d := Demo{T: MyTime(time.Now())} s, _ := json.Marshal(d) fmt.Println(string(s)) } |
输出:
1 |
{"T":{}} |
现在,我们实现序列化接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
type MyTime time.Time type Demo struct { T MyTime } func (myT MyTime) MarshalText() (data []byte, err error) { t := time.Time(myT) data = []byte(t.Format("2006-01-02 15:04:05")) return } func (myT *MyTime) UnmarshalText(text []byte) (err error) { t := (*time.Time)(myT) *t, err = time.Parse("2006-01-02 15:04:05", string(text)) return } func main() { d := Demo{T: MyTime(time.Now())} s, _ := json.Marshal(d) fmt.Println(string(s)) } |
结果达到预期:
1 |
{"T":"2020-09-09 18:45:49"} |
非常值得注意的是,反序列化UnmarshalText需要实现在*MyTime指针类型上,因为我们要在内部改变MyTime对象的值。
总结两种序列化interface都有用,需要理解它们的场景区别,基本就是这样。
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~