34

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

 4 years ago
source link: https://www.tuicool.com/articles/qqaQFj2
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.

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)
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK