Golang自定义JSON序列化

在Go语言中,我们可以很方便的使用标准库encoding/json进行结构体的(反)序列化,它会自动帮我们解析嵌套的structs,以及内部的string、int、map等基本类型,但对于某些特殊类型就不是那么好用了。

举例

一个典型就是标准库time.Time,在结构体中使用该类型表达时间,对后续编程操作非常方便。

但是如果我们对它进行JSON编码,你会发现序列化后的时间格式并不是我们想要的,代码如下:

输出如下:

我们当然希望时间格式编码为这样:

这里就要首先搞明白json.Marshal方法是如何编码time.Time字段的,才能进一步解决自定义格式问题。

time.Time编码原理

json.Marshal会反射传入的Demo对象,查看它里面的每个字段,然后遇到了T time.Time。

对于内置类型(例如int、string等),它会直接编码,但现在是time.Time自定义类型,它会做点额外的判定,说明如下:

它会反射判定2个事实,首先是看一下time.Time有没有实现如下接口:

如果有的话,那么就会执行time.Time.MarshalJSON()得到自定义序列化后的字符串。

如果没有实现该接口,还会再判定有没有实现如下接口:

作用一样,也是自定义序列化,返回一个字符串。

那么这俩接口有啥区别呢?不如看看time.Time类型的实现,它把两者都实现了,怎么用就是上层的问题了:

核心区别在于,MarshalJSON需要返回带引号的字符串:

可以想想encoding/json库会直接把返回值拼到最终JSON串上。

MarshalText则简单的多,直接返回不带引号的部分即可:

所以总结一下,如果我们自定义的类型想序列化成int这种数字,只能用MarshalJson来返回一个例如”5″这样的字符串(注意没有嵌套引号)。

改变time.Time序列化实现

我们可以通过给time.Time定义一个类型别名,然后给新类型自定义MarshalText方法,同时还能兼顾到time.Time原生方法的调用,一举两得。

如果我们仅仅定义新类型,那么JSON库反射新类型并不能找到序列化接口的实现,因此结果是空的:

输出:

现在,我们实现序列化接口:

结果达到预期:

非常值得注意的是,反序列化UnmarshalText需要实现在*MyTime指针类型上,因为我们要在内部改变MyTime对象的值。

总结

两种序列化interface都有用,需要理解它们的场景区别,基本就是这样。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~