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