阿小信大人的头像
Talk is cheap. Show me the code. Linus Torvalds

Golang Gorm中自定义Time类型的JSON字段格式2018-11-05 13:18

Golang中使用gorm时,通过加入gorm.Model到自己的struct来定义一个model。 Gorm是这样定义Model的:

type Model struct {
    ID        uint `gorm:"primary_key"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt *time.Time `sql:"index"`
}

当我们的API在接收到查询请求时返回一个model通过JSON形式返回给客户端,这时类型为time.Time的字段就会默认以RFC3339的格式返回,所有的这类字段的返回值都固定是2006-01-02T15:04:05.999999999Z07:00这种格式

const (
    ANSIC       = "Mon Jan _2 15:04:05 2006"
    UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
    RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
    RFC822      = "02 Jan 06 15:04 MST"
    RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
    RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
    RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
    RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
    RFC3339     = "2006-01-02T15:04:05Z07:00"
    RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
    Kitchen     = "3:04PM"
    // Handy time stamps.
    Stamp      = "Jan _2 15:04:05"
    StampMilli = "Jan _2 15:04:05.000"
    StampMicro = "Jan _2 15:04:05.000000"
    StampNano  = "Jan _2 15:04:05.000000000"
)

原因时在go的time包中实现json.Marshaler接口时指定了使用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
}

所以在把model序列化为JSON的时候默认就会来调这个MarshalJSON方法把time.Time类型的字段都搞成这种格式,要想自定义这种格式只能重写这个接口方法,go中不允许在包外新增或重写方法 cannot define new methods on non-local type[s],,只能通过在外部定义别名或者内嵌结构体,再在这上面新加或重写方法。

别名方式:

type JSONTime time.Time

内嵌方式(推荐):

type JSONTime struct {
    time.Time
}

需要注意别名方式只能使用原始类型的字段,不能使用其方法,只重写字段的时候可以考虑使用,建议使用内嵌方式,都可以使用 https://stackoverflow.com/questions/28800672/how-to-add-new-methods-to-an-existing-type-in-go

所以我们只需定义一个内嵌time.Time的结构体,并重写MarshalJSON方法,然后在定义model的时候把time.Time类型替换为我们自己的类型即可。但是在gorm中只重写MarshalJSON是不够的,只写这个方法会在写数据库的时候会提示delete_at字段不存在,需要加上database/sql的Value和Scan方法 https://github.com/jinzhu/gorm/issues/1611#issuecomment-329654638

所以最后实现我们自己格式化的time类型JSONTime为:

package utils
import (
    "database/sql/driver"
    "fmt"
    "time"
)

// JSONTime format json time field by myself
type JSONTime struct {
    time.Time
}

// MarshalJSON on JSONTime format Time field with %Y-%m-%d %H:%M:%S
func (t JSONTime) MarshalJSON() ([]byte, error) {
    formatted := fmt.Sprintf("\"%s\"", t.Format("2006-01-02 15:04:05"))
    return []byte(formatted), nil
}

// Value insert timestamp into mysql need this function.
func (t JSONTime) 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 *JSONTime) Scan(v interface{}) error {
    value, ok := v.(time.Time)
    if ok {
        *t = JSONTime{Time: value}
        return nil
    }
    return fmt.Errorf("can not convert %v to timestamp", v)
}

然后重新定义自己的BaseModel:

package models

import (
    "github.com/axiaoxin/gin-skeleton/app/utils"
)

type BaseModel struct {
    ID        uint            `gorm:"primary_key" json:"id"`
    CreatedAt utils.JSONTime  `json:"createdAt"`
    UpdatedAt utils.JSONTime  `json:"updatedAt"`
    DeletedAt *utils.JSONTime `sql:"index" json:"-"`
}

在定义model时,不再内嵌gorm.Model,而是使用models.BaseModel代替,这样在接口中返回的JSON中时间类型的字段就是我们自定义的格式了。

编译型语言的这种类型限制了不能动态的将这个时间类型字段转换为字符串,有好处也有不便之处。

如果您觉得从我的分享中得到了帮助,并且希望我的博客持续发展下去,请点击支付宝捐赠,谢谢!

若非特别声明,文章均为阿小信的个人笔记,转载请注明出处。文章如有侵权内容,请联系我,我会及时删除。

#Golang#   阅读[10375] 评论[2]

你可能也感兴趣的文章推荐

本文最近访客

网友207.*.*.143[Redmond]2021-12-05 10:30
网友157.*.*.70[Redmond]2021-12-05 10:30
网友157.*.*.140[Redmond]2021-12-05 10:30
网友157.*.*.177[Redmond]2021-12-05 10:30

发表评论

#1 网友59.*.*.237[火星]99243 :
想请问一下我要创建一个模型实例时,createAt 时怎么赋值呢 CreateTime: JSONTime{time.Now()}, 这样赋值后数据显示0000-00-00 00:00:00
2019-08-02 20:03 回复
#2 网友113.*.*.255[火星]112602 回复 #1 网友59.*.*.237[火星] :
应该不会是0值才对 我在实际使用中就是这样写的 s.UpdatedAt = JSONTime{Time: time.Now()}
2020-04-02 14:40 回复