26

修改go的时间类型time.Time序列化为时间戳——以及更通用的自定义json序列化方式

 4 years ago
source link: https://studygolang.com/articles/29909
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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()方法,是为了解决反射修改值的可达性问题, 参考这里写的反射第三定律

微信扫码关注站长公众号,和站长交流学习

bUJrayM.jpg!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK