Go 对象扩展与Gorm JSON 时间格式化
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)
}