内容简介: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()方法,是为了解决反射修改值的可达性问题, 参考这里写的反射第三定律 。
微信扫码关注站长公众号,和站长交流学习
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。