解决方案

实现 MarshalJSON 接口

type Log struct {
  Id        int64     `json:"id"`
  UserId    string    `json:"user_id"`
  Operation string    `json:"operation"`
  Result    string    `json:"result"`
  CreatedAt time.Time `json:"created_at"`
}

func (log *Log) MarshalJSON() ([]byte, error) {
  type Alias Log
  return json.Marshal(&struct {
    Alias
    CreatedAt string `json:"created_at"`
  }{
    Alias:     Alias(*log),
    CreatedAt: log.CreatedAt.Format("2006-01-02 15:04:05"),
  })
}

这种方式比较简单,但是如果你有很多 struct 都包含时间,那么需要为每个 struct 实现 MarshalJSON,比较麻烦。并且在反向序列化时也存在 created_at 为 null 则反序列化失败的问题。

至于为什么时间格式是 2006-01-02 15:04:05,可以参考 time 这个包的 fromat.go 中定义的常量。

自已定一个 Time 数据类型

package utils

import (
  "time"
)

type (
  Time time.Time
)

const DefaultTimeFormat = "2006-01-02 15:04:05"

func (t *Time) UnmarshalJSON(data []byte) error {
  now, err := time.ParseInLocation(`"`+DefaultTimeFormat+`"`, string(data), time.Local)
  *t = Time(now)
  return err
}

func (t *Time) MarshalJSON() ([]byte, error) {
  if time.Time(*t).IsZero() {
    return []byte("null"), nil
  }
  b := make([]byte, 0, len(DefaultTimeFormat)+2)
  b = append(b, '"')
  b = time.Time(*t).AppendFormat(b, DefaultTimeFormat)
  b = append(b, '"')
  return b, nil
}

func (t Time) String() string {
  return time.Time(t).Format(DefaultTimeFormat)
}

func (t *Time) IsZero() bool {
  return time.Time(*t).Second() == 0 && time.Time(*t).Nanosecond() == 0
}

定义好时间的类型后,在其他的 struct 中使用该类型作为时间的类型。

Node struct {
  Id          string               `json:"id"`
  Name        string               `json:"name"`
  CreatedAt   utils.Time           `json:"created_at"`
  UpdatedAt   utils.Time           `json:"updated_at"`
}

这样当 Node 在序列化和反序列化时会自动调用自定义 utils.TimeMarshalJSONUnmarshalJSON 方法。

注意:在 MarshalJSON 方法中,如果你的 utils.Time 为空,那么将其转换为 null,而不是 0001-01-01 00:00:00 +0000 UTC。

如果你有更好的方案,请告诉我,谢谢!

I hope this is helpful, Happy hacking…