0. 問題
go的json對Time類型的序列化結(jié)果是2020-07-16T14:49:50.3269159+08:00
這種類型伺帘。我們希望改成時間戳集畅。
1. 網(wǎng)上有各種現(xiàn)成的做法
1.1 輔助結(jié)構(gòu)體
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))
}
每個結(jié)構(gòu)體都要寫一個輔助結(jié)構(gòu)體债蓝,碼字量翻倍耗美,如果公司按照代碼行數(shù)算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
}
本質(zhì)上和1.1沒有什么區(qū)別腌乡,就是代碼行數(shù)少了盟劫。
注意一個問題,如果這里不用別名而直接用SelfUser類
tmp := &struct{
*SelfUser
CreateTime int64 `json:"createTime"`
} {}
會造成SelfUser反序列化調(diào)用無限嵌套与纽,最后棧溢出侣签。
1.3 受1.2啟發(fā),縮小修改范圍渣锦,直接創(chuàng)建一個Time的別名類
上面的方法需要在每個結(jié)構(gòu)體里面去做一個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())
}
執(zhí)行輸出:
=== 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)
}
執(zhí)行輸出:
=== 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)
}
這個方法還有一個很大的優(yōu)點就是不影響現(xiàn)有框架例如ORM框架在映射數(shù)據(jù)庫日期類時對日期類的解析皆看。
1.4 直接創(chuàng)建一個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)
}
執(zhí)行輸出:
=== 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)
}
執(zhí)行輸出:
=== 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匿名結(jié)構(gòu)體的特性實現(xiàn)了Time對time.Time的 “偽繼承” (go沒有繼承背零,只是看起來很像)腰吟,這樣 Time是可以調(diào)用time.Time的所有方法的,所以我們看到log.Printf("%v\n", *user2)
輸出的是{0 testUser 2020-07-16 15:58:51 +0800 CST}
,因為Time有String方法毛雇。
缺點是Time不再是time.Time類嫉称,使用ORM框架時無法映射數(shù)據(jù)庫的日期類了,會報錯unsupported Scan, storing driver.Value type time.Time into type *main_test.Time
灵疮。
2. 自定義每個結(jié)構(gòu)體的MarshalJSON和UnmarshalJSON方法
一開始腦筋沒轉(zhuǎn)過彎來织阅,想著把需要使用自定義json的參數(shù)所在的結(jié)構(gòu)體重寫一套通用的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))
// 每種數(shù)據(jù)類型都要針對性處理润樱,暫時就只寫int64、string诉植、Time了
switch reType.Field(i).Type.String() {
case "time.Time":
// 接口對象通過.(a)就轉(zhuǎn)換成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)
}
執(zhí)行輸出:
=== 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
這里有個重點內(nèi)容,UnmarshalJSON方法里面
reValue := reflect.ValueOf(s)
其他都寫的值反射晾腔,即s是值舌稀,這里s是指針,然后后面value再調(diào)用Elem()方法灼擂,是為了解決反射修改值的可達性問題壁查,參考這里寫的反射第三定律。