美文网首页
修改go的时间类型time.Time序列化为时间戳——以及更通用

修改go的时间类型time.Time序列化为时间戳——以及更通用

作者: 猫尾草 | 来源:发表于2020-07-15 17:48 被阅读0次

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

    相关文章

      网友评论

          本文标题:修改go的时间类型time.Time序列化为时间戳——以及更通用

          本文链接:https://www.haomeiwen.com/subject/lzkmhktx.html