本文详解如何在 Go 中安全实现支持 JSON 和 BSON 序列化的自定义 Timestamp 类型,解决因方法接收器类型不匹配导致的编译错误,并提供可直接复用的构造方式与最佳实践。
本文详解如何在 go 中安全实现支持 json 和 bson 序列化的自定义 `timestamp` 类型,解决因方法接收器类型不匹配导致的编译错误,并提供可直接复用的构造方式与最佳实践。
在 Go 应用开发中,尤其是对接 MongoDB(通过 mgo 或官方 driver)和 REST API 时,常需对时间字段进行统一格式化处理——例如将 time.Time 序列化为 Unix 时间戳整数(而非 ISO8601 字符串)。为此,许多开发者会封装一个 Timestamp 类型。但若方法接收器声明不当,极易引发类似 invalid indirect of t (type Timestamp) 的编译错误。根本原因在于:值接收器无法对结构体/类型本身取地址,而 GetBSON 和 SetBSON 等方法内部需修改或解引用 t,必须使用指针接收器。
以下是修正后的、生产可用的 Timestamp 实现(已适配现代 Go 模块与标准库,移除了过时的 labix.org/v2/mgo/bson 依赖,推荐使用 go.mongodb.org/mongo-driver/bson 或保持兼容性):
package timestamp
import (
"encoding/json"
"fmt"
"strconv"
"time"
)
// Timestamp 是 time.Time 的别名,用于定制序列化行为
type Timestamp time.Time
// MarshalJSON 将时间序列化为 Unix 秒级时间戳(int64 → string)
func (t *Timestamp) MarshalJSON() ([]byte, error) {
if t == nil {
return []byte("null"), nil
}
ts := time.Time(*t).Unix()
return []byte(fmt.Sprintf("%d", ts)), nil
}
// UnmarshalJSON 从 Unix 时间戳字符串反序列化为 Timestamp
func (t *Timestamp) UnmarshalJSON(b []byte) error {
if len(b) == 0 || string(b) == "null" {
*t = Timestamp{}
return nil
}
s := string(b)
// 去除引号(JSON 字符串格式如 "1717023456")
if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' {
s = s[1 : len(s)-1]
}
ts, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return fmt.Errorf("failed to parse timestamp: %w", err)
}
*t = Timestamp(time.Unix(ts, 0))
return nil
}
// GetBSON(mgo 兼容):返回 time.Time 供 BSON 编码,需指针接收器
func (t *Timestamp) GetBSON() (interface{}, error) {
if t == nil || time.Time(*t).IsZero() {
return nil, nil
}
return time.Time(*t), nil
}
// SetBSON(mgo 兼容):从 BSON raw data 解析为 Timestamp
func (t *Timestamp) SetBSON(raw bson.Raw) error {
var tm time.Time
if err := raw.Unmarshal(&tm); err != nil {
return err
}
*t = Timestamp(tm)
return nil
}
// String 实现 fmt.Stringer 接口,便于日志调试
func (t Timestamp) String() string {
if time.Time(t).IsZero() {
return "(zero time)"
}
return time.Time(t).Format(time.RFC3339)
}
// 辅助构造函数(强烈推荐)
func Now() *Timestamp {
now := time.Now()
return (*Timestamp)(&now)
}
func New(t time.Time) *Timestamp {
return (*Timestamp)(&t)
}✅ 关键修复点:
- GetBSON 和 SetBSON 方法签名必须为 func (t *Timestamp) ...(指针接收器),否则 *t 解引用非法;
- UnmarshalJSON 中增加 nil 和 "null" 容错,避免 panic;
- MarshalJSON 增加 nil 检查,确保空指针安全;
- 补充 Now() 和 New() 构造函数,大幅提升使用便捷性与可读性。
在模型中使用时,应严格匹配指针类型:
立即学习“go语言免费学习笔记(深入)”;
import "your-module/timestamp"
type User struct {
Name string `bson:"name" json:"name"`
CreatedAt *timestamp.Timestamp `bson:"created_at,omitempty" json:"created_at,omitempty"`
UpdatedAt *timestamp.Timestamp `bson:"updated_at,omitempty" json:"updated_at,omitempty"`
}
// 创建实例(推荐方式)
u := User{
Name: "Alice",
CreatedAt: timestamp.Now(), // ✅ 清晰、安全、零错误
UpdatedAt: timestamp.New(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)),
}
// 或显式转换(不推荐,易出错)
// now := time.Now()
// u := User{CreatedAt: (*timestamp.Timestamp)(&now)}⚠️ 注意事项:
- 若使用官方 MongoDB Go Driver(go.mongodb.org/mongo-driver/bson),请将 GetBSON/SetBSON 替换为 MarshalBSON/UnmarshalBSON 方法,并遵循其接口规范;
- Timestamp 不应嵌入 time.Time(如 type Timestamp struct { time.Time }),否则失去类型隔离性与序列化控制权;
- 在 HTTP 响应中返回 Unix 时间戳是常见需求,但需与前端约定好单位(秒 or 毫秒),本例默认秒级,如需毫秒,将 Unix() 改为 UnixMilli() 并同步调整 UnmarshalJSON 解析逻辑。
掌握此模式后,你不仅能彻底规避编译错误,还能构建出类型安全、序列化可控、易于维护的时间字段抽象,为微服务与数据持久层打下坚实基础。










