内容简介:go的json对Time类型的序列化结果是每个结构体都要写一个辅助结构体,码字量翻倍,如果公司按照代码行数算kpi这倒是一个好方法在1.1的基础上把MarshalJSON和UnmarshalJSON方法修改一下:
0. 问题
go的json对Time类型的序列化结果是 2020-07-16T14:49:50.3269159+08:00 这种类型。我们希望改成时间戳。
1. 网上有各种现成的做法
1.1 辅助结构体
package main_test
import (
"encoding/json"
"log"
"testing"
"time"
)
type SelfUser struct {
ID int64 `json:"id"`
Name string `json:"name"`
CreateTime time.Time `json:"createTime"`
}
func (u *SelfUser) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
ID int64 `json:"id"`
Name string `json:"name"`
CreateTime int64 `json:"createTime"`
}{
ID: u.ID,
Name: u.Name,
CreateTime: u.CreateTime.Unix(),
})
}
func (s *SelfUser) UnmarshalJSON(data []byte) error {
tmp := &struct{
ID int64 `json:"id"`
Name string `json:"name"`
CreateTime int64 `json:"createTime"`
} {}
err := json.Unmarshal(data, tmp)
if err != nil {
return err
}
s.ID = tmp.ID
s.Name = tmp.Name
s.CreateTime = time.Unix(tmp.CreateTime, 0)
return nil
}
func TestJson3(t *testing.T) {
user := &SelfUser{
ID: 0,
Name: "testUser",
CreateTime: time.Now(),
}
res, err := json.Marshal(user)
if err != nil {
log.Fatal(err)
}
log.Printf("%v", string(res))
}
每个结构体都要写一个辅助结构体,码字量翻倍,如果公司按照代码行数算kpi这倒是一个好方法
1.2 使用别名
在1.1的基础上把MarshalJSON和UnmarshalJSON方法修改一下:
func (s *SelfUser) MarshalJSON() ([]byte, error) {
type Alias SelfUser
return json.Marshal(&struct {
CreateTime int64 `json:"createTime"`
*Alias
}{
CreateTime: s.CreateTime.Unix(),
Alias: (*Alias)(s),
})
}
func (s *SelfUser) UnmarshalJSON(data []byte) error {
type Alias SelfUser
tmp := &struct{
*Alias
CreateTime int64 `json:"createTime"`
} {}
err := json.Unmarshal(data, tmp)
if err != nil {
return err
}
s.ID = tmp.ID
s.Name = tmp.Name
s.CreateTime = time.Unix(tmp.CreateTime, 0)
return nil
}
本质上和1.1没有什么区别,就是代码行数少了。
注意一个问题,如果这里不用别名而直接用SelfUser类
tmp := &struct{
*SelfUser
CreateTime int64 `json:"createTime"`
} {}
会造成SelfUser反序列化调用无限嵌套,最后栈溢出。
1.3 受1.2启发,缩小修改范围,直接创建一个Time的别名类
上面的方法需要在每个结构体里面去做一个Time的别名类,为什么不直接做一个公共的Time别名类呢?
package main_test
import (
"encoding/json"
"log"
"strconv"
"testing"
"time"
)
type Time time.Time
func (t *Time) UnmarshalJSON(data []byte) (err error) {
num, err := strconv.Atoi(string(data))
if err != nil {
return err
}
*t = Time(time.Unix(int64(num), 0))
return
}
func (t Time) MarshalJSON() ([]byte, error) {
return ([]byte)(strconv.FormatInt(time.Time(t).Unix(), 10)), nil
}
func TestJson3(t *testing.T) {
dateTime := Time(time.Now())
res, err := json.Marshal(dateTime)
if err != nil {
log.Fatal(err)
}
log.Println(string(res))
dateTime2 := Time(time.Time{})
err = json.Unmarshal(res, &dateTime2)
log.Printf("%v\n", time.Time(dateTime2).String())
}
执行输出:
=== RUN TestJson3
2020/07/16 16:07:28 1594886848
2020/07/16 16:07:28 {0 63730483648 0x9b26c0}
--- PASS: TestJson3 (0.01s)
PASS
我们在SelfUser中使用这个类:
package main_test
import (
"encoding/json"
"log"
"strconv"
"testing"
"time"
)
type Time time.Time
func (t *Time) UnmarshalJSON(data []byte) (err error) {
num, err := strconv.Atoi(string(data))
if err != nil {
return err
}
*t = Time(time.Unix(int64(num), 0))
return
}
func (t Time) MarshalJSON() ([]byte, error) {
return ([]byte)(strconv.FormatInt(time.Time(t).Unix(), 10)), nil
}
type SelfUser struct {
ID int64 `json:"id"`
Name string `json:"name"`
CreateTime Time `json:"createTime"`
}
func TestJson3(t *testing.T) {
user := &SelfUser{
ID: 0,
Name: "testUser",
CreateTime: Time(time.Now()),
}
res, err := json.Marshal(user)
if err != nil {
log.Fatal(err)
}
log.Printf("%v\n", string(res))
user2 := &SelfUser{}
err = json.Unmarshal(res, user2)
if err != nil {
log.Fatal(err)
}
log.Printf("%v\n", *user2)
}
执行输出:
=== RUN TestJson3
2020/07/16 16:06:19 {"id":0,"name":"testUser","createTime":1594886779}
2020/07/16 16:06:19 {0 testUser {0 63730483579 0x9b26c0}}
--- PASS: TestJson3 (0.01s)
PASS
这个方法有一个问题, log.Printf("%v\n", *user2) 输出的是 {0 testUser {0 63730481503 0x9b26c0}} ,而如果直接使用time.Time类则会输出 {0 testUser 2020-07-16 15:33:56.9806447 +0800 CST} ,修改之后不直观了。
这个问题可以忽略不计,或者自己写一下Time的String方法,如下:
const (
timeFormart = "2006-01-02 15:04:05"
)
func (t Time) String() string{
b := make([]byte, 0, len(timeFormart))
b = time.Time(t).AppendFormat(b, timeFormart)
return string(b)
}
这个方法还有一个很大的优点就是不影响现有框架例如ORM框架在映射数据库日期类时对日期类的解析。
1.4 直接创建一个Time的匿名继承类
package main_test
import (
"encoding/json"
"log"
"strconv"
"testing"
"time"
)
type Time struct {
time.Time
}
func (t *Time) UnmarshalJSON(data []byte) error {
num, err := strconv.Atoi(string(data))
if err != nil {
return err
}
t.Time = time.Unix(int64(num), 0)
return nil
}
func (t Time) MarshalJSON() ([]byte, error) {
return ([]byte)(strconv.FormatInt(t.Time.Unix(), 10)), nil
}
func TestJson3(t *testing.T) {
var dateTime Time
dateTime.Time = time.Now()
res, err := json.Marshal(dateTime)
if err != nil {
log.Fatal(err)
}
log.Println(string(res))
var dateTime2 Time
err = json.Unmarshal(res, &dateTime2)
log.Printf("%v\n", dateTime2)
}
执行输出:
=== RUN TestJson3 2020/07/16 15:47:59 1594885679 2020/07/16 15:47:59 2020-07-16 15:47:59 +0800 CST --- PASS: TestJson3 (0.01s) PASS
我们在SelfUser中使用这个类:
package main_test
import (
"encoding/json"
"log"
"strconv"
"testing"
"time"
)
type Time struct {
time.Time
}
func (t *Time) UnmarshalJSON(data []byte) error {
num, err := strconv.Atoi(string(data))
if err != nil {
return err
}
t.Time = time.Unix(int64(num), 0)
return nil
}
func (t Time) MarshalJSON() ([]byte, error) {
return ([]byte)(strconv.FormatInt(t.Time.Unix(), 10)), nil
}
type SelfUser struct {
ID int64 `json:"id"`
Name string `json:"name"`
CreateTime Time `json:"createTime"`
}
func TestJson3(t *testing.T) {
user := &SelfUser{
ID: 0,
Name: "testUser",
}
var dateTime Time
dateTime.Time = time.Now()
user.CreateTime = dateTime
res, err := json.Marshal(user)
if err != nil {
log.Fatal(err)
}
log.Printf("%v\n", string(res))
user2 := &SelfUser{}
err = json.Unmarshal(res, user2)
if err != nil {
log.Fatal(err)
}
log.Printf("%v\n", *user2)
}
执行输出:
=== RUN TestJson3
2020/07/16 15:58:51 {"id":0,"name":"testUser","createTime":1594886331}
2020/07/16 15:58:51 {0 testUser 2020-07-16 15:58:51 +0800 CST}
--- PASS: TestJson3 (0.02s)
PASS
相比1.3,使用Golang匿名结构体的特性实现了Time对time.Time的 “伪继承” (go没有继承,只是看起来很像),这样 Time是可以调用time.Time的所有方法的,所以我们看到 log.Printf("%v\n", *user2) 输出的是 {0 testUser 2020-07-16 15:58:51 +0800 CST} ,因为Time有String方法。
缺点是Time不再是time.Time类,使用ORM框架时无法映射数据库的日期类了,会报错 unsupported Scan, storing driver.Value type time.Time into type *main_test.Time 。
2. 自定义每个结构体的MarshalJSON和UnmarshalJSON方法
一开始脑筋没转过弯来,想着把需要使用自定义json的参数所在的结构体重写一套通用的MarshalJSON和UnmarshalJSON方法,写的很艰难。代码如下:
package main_test
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"log"
"reflect"
"strconv"
"strings"
"testing"
"time"
)
type VssUser struct {
Id int64 `json:"id"`
Name string `json:"name"`
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
}
// MarshalJSON 序列化方法
func (s *VssUser) MarshalJSON() ([]byte, error) {
log.Println("自定义json序列化")
buffer := bytes.NewBufferString("{")
reType := reflect.TypeOf(*s)
reValue := reflect.ValueOf(*s)
count := reType.NumField() - 1
for i := 0; i < reType.NumField(); i++ {
jsonKey := getJsonKey(reType.Field(i))
jsonValue, err := getJsonValue(reValue.Field(i))
if err != nil {
return nil, err
}
buffer.WriteString(fmt.Sprintf("\"%v\":%v", jsonKey, string(jsonValue)))
if i < count {
buffer.WriteString(",")
}
}
buffer.WriteString("}")
return buffer.Bytes(), nil
}
// getJsonKey 获取json的key,不考虑忽略默认值的事,不管omitempty标签
func getJsonKey(field reflect.StructField) string {
jsonTag := field.Tag.Get("json")
if len(jsonTag) == 0 {
return field.Name
} else {
return strings.Split(jsonTag, ",")[0]
}
}
func getJsonValue(value reflect.Value) ([]byte, error) {
// 指针需要使用Elem取值
if value.Kind() == reflect.Ptr {
return jsonValue(value.Elem())
} else {
return jsonValue(value)
}
}
func jsonValue(value reflect.Value) ([]byte, error) {
// time.Time类型特殊处理,改为时间戳
if value.Type().String() == "time.Time" {
method := value.MethodByName("Unix")
in := make([]reflect.Value, 0)
rtn := method.Call(in)
return ([]byte)(strconv.FormatInt(rtn[0].Int(), 10)), nil
} else {
return json.Marshal(value.Interface())
}
}
func (s *VssUser) UnmarshalJSON(data []byte) error {
log.Println("自定义json反序列化")
// 先全部用接口接收
commonArr := make(map[string]interface{})
err := json.Unmarshal(data, &commonArr)
if err != nil {
return err
}
reValue := reflect.ValueOf(s)
reType := reflect.TypeOf(*s)
for i:=0; i<reType.NumField(); i++ {
jsonKey := getJsonKey(reType.Field(i))
// 每种数据类型都要针对性处理,暂时就只写int64、string、Time了
switch reType.Field(i).Type.String() {
case "time.Time":
// 接口对象通过.(a)就转换成a类型,只有接口对象
jsonValue := commonArr[jsonKey].(float64)
time := time.Unix(int64(jsonValue), 0)
reValue.Elem().Field(i).Set(reflect.ValueOf(time))
case "int64":
jsonValue := commonArr[jsonKey].(float64)
reValue.Elem().Field(i).Set(reflect.ValueOf(int64(jsonValue)))
case "string":
jsonValue := commonArr[jsonKey].(string)
reValue.Elem().Field(i).Set(reflect.ValueOf(jsonValue))
default:
return errors.New("value error")
}
}
return nil
}
func TestJson2(t *testing.T) {
vssUser := &VssUser{
Id: 0,
Name: "testUser",
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
res, err := json.Marshal(vssUser)
if err != nil {
log.Fatal(err)
}
log.Println(string(res))
dateTime2 := &VssUser{}
json.Unmarshal(res, &dateTime2)
log.Printf("%v", *dateTime2)
}
执行输出:
=== RUN TestJson2
2020/07/16 17:39:38 自定义json序列化
2020/07/16 17:39:38 {"id":0,"name":"testUser","createTime":1594892378,"updateTime":1594892378}
2020/07/16 17:39:38 自定义json反序列化
2020/07/16 17:39:38 {0 testUser 2020-07-16 17:39:38 +0800 CST 2020-07-16 17:39:38 +0800 CST}
--- PASS: TestJson2 (0.01s)
PASS
这里有个重点内容,UnmarshalJSON方法里面
reValue := reflect.ValueOf(s)
其他都写的值反射,即s是值,这里s是指针,然后后面value再调用Elem()方法,是为了解决反射修改值的可达性问题, 参考这里写的反射第三定律 。
微信扫码关注站长公众号,和站长交流学习
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
High Performance Python
Micha Gorelick、Ian Ozsvald / O'Reilly Media / 2014-9-10 / USD 39.99
If you're an experienced Python programmer, High Performance Python will guide you through the various routes of code optimization. You'll learn how to use smarter algorithms and leverage peripheral t......一起来看看 《High Performance Python》 这本书的介绍吧!