美文网首页
Go 对象扩展与Gorm JSON 时间格式化

Go 对象扩展与Gorm JSON 时间格式化

作者: 坚果jimbowhy | 来源:发表于2019-10-02 17:34 被阅读0次

    JSON 解析与扩展已有类型

    Go 语言是没有完整的 OOP 对象模型的,在 Golang 的世界里没有继承,只有组合和接口,并且是松散的接口结构,不强制声明实现接口。通过对结构体的组合对现有对象进行扩展也是很便利的,参考 interface & struct 接口与结构体。

    单一继承关系解决了 is-a 也就是定义问题,因此可以把子类当做父类来对待。但对于父类不同但又具有某些共同行为的数据,单一继承就不能解决了,C++ 采取了多继承这种复杂的方式。GO 采取的组合方式更贴近现实世界的网状结构,不同于继承,GO 语言的接口是松散的结构,它不和定义绑定。从这一点上来说,Duck Type 相比传统的 extends 是更加松耦合的方式,可以同时从多个维度对数据进行抽象,找出它们的共同点,使用同一套逻辑来处理。

    注意 People.Name 成员首字母大写,否则不会导出,解析 JSON 时不会正确赋值。 如果想在一个包中访问另一个包中结构体的字段,则必须是大写字母开头的变量,即可导出的变量。

    import (
        // "database/sql/driver"
        "encoding/json"
        "fmt"
        "time"
    )
    
    type People struct {
        Name string `json:"name"`
        Time TimeNormal
    }
    
    func main() {
        js := `{
                "name":"Aob"
            }`
        var p People
        err := json.Unmarshal([]byte(js), &p)
        if err != nil {
            fmt.Println("err: ", err)
            return
        }
        fmt.Println("people: ", p)
    
        p.Time = TimeNormal{time.Now()}
        data, err := json.Marshal(p)
        if err != nil {
            fmt.Println("JSON marshaling failed: %s", err)
        }
        fmt.Printf("JSON: %s\n", data)
    
    }
    
    // type TimeNormal time.Time // 别名方式扩展
    type TimeNormal struct { // 内嵌方式(推荐)
        time.Time
    }
    
    func (t TimeNormal) MarshalJSON() ([]byte, error) {
        // tune := fmt.Sprintf(`"%s"`, t.Format("2006-01-02 15:04:05"))
        tune := t.Format(`"2006-01-02 15:04:05"`)
        return []byte(tune), nil
    }
    

    GO 的 time 包中实现 json.Marshaler 接口的序列化方法 MarshalJSON 指定 RFC3339Nano 格式:

    // MarshalJSON implements the json.Marshaler interface.
    // The time is a quoted string in RFC 3339 format, with sub-second precision added if present.
    func (t Time) MarshalJSON() ([]byte, error) {
        if y := t.Year(); y < 0 || y >= 10000 {
            // RFC 3339 is clear that years are 4 digits exactly.
            // See golang.org/issue/4556#c15 for more discussion.
            return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]")
        }
    
        b := make([]byte, 0, len(RFC3339Nano)+2)
        b = append(b, '"')
        b = t.AppendFormat(b, RFC3339Nano)
        b = append(b, '"')
        return b, nil
    }
    

    可以使用格式化函数进行转换,下面是12H、24H两种格式的转换,年份和小时格式代码分别是06、03,使用4位数年份就是 2006,使用24H制就是 15:

    time.Now().Format("06-01-02 03:04:05")
    time.Now().Format("2006-01-02 15:04:05")
    

    也可以直接给 Format 函数传入格式类型:

    time.ANSIC:       Fri Aug  2 23:02:02 2019
    time.UnixDate:    Fri Aug  2 23:02:02 CST 2019
    time.RFC1123:     Fri, 02 Aug 2019 23:02:02 CST
    time.RFC3339:     2019-08-02T23:02:02+08:00
    time.RFC822:      02 Aug 19 23:02 CST
    time.RFC850:      Friday, 02-Aug-19 23:02:02 CST
    time.RFC1123Z:    Fri, 02 Aug 2019 23:02:02 +0800
    time.RFC3339Nano: 2019-08-02T23:02:02.6227628+08:00
    time.RFC822Z:     02 Aug 19 23:02 +0800
    time.Kitchen:     11:02PM
    time.Stamp:       Aug  2 23:02:02
    time.StampMicro:  Aug  2 23:02:02.629703
    time.StampMilli:  Aug  2 23:02:02.631
    time.StampNano:   Aug  2 23:02:02.631646200
    

    Go 不允许在包外新增或重写方法 cannot define new methods on non-local type,只能通过在外部定义别名或者内嵌结构体进行内置对象的扩展。需要注意别名方式只能使用原始类型的字段,不能使用其方法,只重写字段的时候可以考虑使用。

    在 gorm 中只重写 MarshalJSON 是不够的,因为 ORM 在插入记录、读取记录时需要的相应执行 Value 和 Scan 方法,需要引入 database/sql/driver 包。为了方便使用,可以定义一个 BaseModel 来替代 gorm.Model。

    import "database/sql/driver"
    
    type TimeNormal struct { // 内嵌方式(推荐)
        time.Time
    }
    
    func (t TimeNormal) MarshalJSON() ([]byte, error) {
        // tune := fmt.Sprintf(`"%s"`, t.Format("2006-01-02 15:04:05"))
        tune := t.Format(`"2006-01-02 15:04:05"`)
        return []byte(tune), nil
    }
    
    // Value insert timestamp into mysql need this function.
    func (t TimeNormal) Value() (driver.Value, error) {
        var zeroTime time.Time
        if t.Time.UnixNano() == zeroTime.UnixNano() {
            return nil, nil
        }
        return t.Time, nil
    }
    
    // Scan valueof time.Time
    func (t *TimeNormal) Scan(v interface{}) error {
        value, ok := v.(time.Time)
        if ok {
            *t = TimeNormal{Time: value}
            return nil
        }
        return fmt.Errorf("can not convert %v to timestamp", v)
    }
    
    type BaseModel struct {
        // gorm.Model
        ID        uint        `gorm:"primary_key" json:"id"`
        CreatedAt TimeNormal  `json:"createdAt"`
        UpdatedAt TimeNormal  `json:"updatedAt"`
        DeletedAt *TimeNormal `sql:"index" json:"-"`
    }
    

    下面是别名方式扩展的核心代码示例,注意类型的转,类型断言和返回类型。访问时间对象时,内嵌方式是 t.Time,使用别名方式后时类型转换 time.Time(t),而且 Scan 方法中不能直接通过类型断言 v.(TimeNormal) 将接口转换到 TimeNormal。另外,设置别名后,TimeNormal 并不能直接使用原始类型 time.Time 的各种方法和成员,需要先进行类型转换。显然,通过结构体匿名嵌入的方式并不存在这样的不便,这种方式可以很好的保持对象的原有性质。

    type TimeNormal time.Time // 别名方式扩展
    
    func (t TimeNormal) MarshalJSON() ([]byte, error) {
        ti := time.Time(t)
        tune := ti.Format(`"2006-01-02 15:04:05"`)
        return []byte(tune), nil
    }
    
    // Value insert timestamp into mysql need this function.
    func (t TimeNormal) Value() (driver.Value, error) {
        var zeroTime time.Time
        ti := time.Time(t)
        if ti.UnixNano() == zeroTime.UnixNano() {
            return nil, nil
        }
        return ti, nil
    }
    
    // Scan valueof time.Time
    func (t *TimeNormal) Scan(v interface{}) error {
        ti, ok := v.(time.Time) // NOT directly assertion v.(TimeNormal)
        if ok {
            *t = TimeNormal(ti)
            return nil
        }
        return fmt.Errorf("can not convert %v to timestamp", v)
    }
    

    相关文章

      网友评论

          本文标题:Go 对象扩展与Gorm JSON 时间格式化

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