gormというORMで、デフォルトで定義されている論理削除可能な構造体は、下記のようなものですが、
// gorm.Model
type Model struct {
ID uint64 `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
これをクライアント側に返却するとき、時刻をJSONエンコードなりTextエンコードなりすると思いますが、
そのまま使うと時刻文字列のフォーマットが、RFC3339Nano
というナノ秒まで持つフォーマットを使って変換されてしまいます。
// Time Format
const (
RFC3339 = "2006-01-02T15:04:05Z07:00"
RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
)
// JSON Encode
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]")
}
return []byte(t.Format(`"` + RFC3339Nano + `"`)), nil
}
クライアントによっては、ナノ秒など解釈できないものがあるためかなり不便です。
普通そんな細かい精度の時刻など必要としない場合の方が多いため、 ミリ秒以下を切り捨てたいところです。
そんなときは、時刻のタイプ自体をカスタマイズすることで解決することが出来ます。
package typedef
import (
"database/sql/driver"
"errors"
"time"
)
// XXX: アプリでナノ秒は必要ないので秒まででフォーマット
//{
// "createdAt": "2015-08-09T15:03:05.135166971Z",
// "objectID": "55c112a40000a1"
//}
// See: http://qiita.com/taizo/items/2c3a338f1aeea86ce9e2
type ResourceTime struct {
time.Time
}
func (rt ResourceTime) MarshalJSON() ([]byte, error) {
t := rt.Time
if y := t.Year(); y < 0 || y >= 10000 {
return nil, errors.New("ResourceTime.MarshalJSON: year outside of range [0,9999]")
}
return []byte(t.Format(`"` + time.RFC3339 + `"`)), nil
}
func (rt ResourceTime) MarshalText() ([]byte, error) {
t := rt.Time
if y := t.Year(); y < 0 || y >= 10000 {
return nil, errors.New("Time.MarshalText: year outside of range [0,9999]")
}
return []byte(t.Format(time.RFC3339)), nil
}
// Sql driver interface
func (rt *ResourceTime) Scan(value interface{}) error {
rt.Time = value.(time.Time)
return nil
}
func (rt ResourceTime) Value() (driver.Value, error) {
return rt.Time, nil
}
やってることは、
- エンコードの時刻文字列フォーマットを秒数まで出力するものに変更
- SQL Driverが実装すべきメソッドを定義(
Scan
とValue
)
これだけでクライアントに返却する時刻文字列のフォーマットを変更することができました。
type Model struct {
ID uint64 `gorm:"primary_key"`
CreatedAt ResourceTime `json:"createdAt"`
UpdatedAt ResourceTime `json:"updatedAt"`
DeletedAt *time.Time `json:"deletedAt,omitempty"`
}
未解決事項
論理削除で使われる DeleteAt
は、time.Time
へのポインターで定義されていますが、これを *ResourceTime
に変更するとMySQLにカラムは追加されますが、実行時、レコード追加するときにエラーになります。
今のところよくわからなかったので、別途 view object
みたいな構造体を定義して詰め直し、時刻フォーマットを変更する方法で回避しました。
詳細は、この辺参考 JSON and struct composition in Go
参考
環境
- Golang 1.4.2
- MySQL 5.6.26