MongoDB Go Driver v2 如何让 time.Time 解码兼容 BSON double?
数据小陈
结论
可以为 time.Time 注册自定义 ValueDecoder,在遇到 BSON double 时自行转换,其他类型则交给原有解码器处理。需要注意,v2 的 bson.NewRegistry() 创建的是空 Registry,不能直接当作默认 Registry 修改。可以从 bson.NewMgoRegistry() 开始,它包含默认编解码器,但采用 mgo 兼容行为,因此不保证所有类型都与 Driver v2 的标准默认 Registry 完全一致。Registry 文档 · NewMgoRegistry 文档 · v1 到 v2 迁移说明
注册自定义解码器
先从 NewMgoRegistry() 中取得 time.Time 的现有解码器,再注册一个包装后的解码器。下面按问题中给出的数据约定,将 BSON double 视为 Unix 毫秒时间戳。
package main
import (
"fmt"
"reflect"
"time"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/mongo"
"go.mongodb.org/mongo-driver/v2/mongo/options"
)
var timeType = reflect.TypeOf(time.Time{})
func newRegistry() (*bson.Registry, error) {
reg := bson.NewMgoRegistry()
// 在覆盖注册之前保存原有解码器,供非 double 值使用。
defaultTimeDecoder, err := reg.LookupDecoder(timeType)
if err != nil {
return nil, fmt.Errorf("查找 time.Time 解码器失败: %w", err)
}
customTimeDecoder := bson.ValueDecoderFunc(func(
ctx bson.DecodeContext,
vr bson.ValueReader,
val reflect.Value,
) error {
if !val.IsValid() || !val.CanSet() || val.Type() != timeType {
return fmt.Errorf("time.Time 解码目标无效或不可设置")
}
if vr.Type() != bson.TypeDouble {
return defaultTimeDecoder.DecodeValue(ctx, vr, val)
}
ms, err := vr.ReadDouble()
if err != nil {
return err
}
val.Set(reflect.ValueOf(time.Unix(0, int64(ms)*int64(time.Millisecond))))
return nil
})
reg.RegisterTypeDecoder(timeType, customTimeDecoder)
return reg, nil
}
func connect(uri string) (*mongo.Client, error) {
reg, err := newRegistry()
if err != nil {
return nil, err
}
return mongo.Connect(options.Client().
ApplyURI(uri).
SetRegistry(reg))
}
Driver v2 将 BSON Reader、解码上下文和相关接口放在 bson 包中,客户端可以通过 SetRegistry 使用自定义 Registry。v2 的 mongo.Connect 也不再接收 context.Context 参数。v2 Registry 文档 · 迁移说明
使用前确认数据约定
- 上例假定
double存储的是 Unix 毫秒时间戳。如果实际存储的是秒,需要改用秒对应的转换方式。 int64(ms)会截去小数部分;浮点数也无法在任意大的数值范围内精确表示整数毫秒。若需要精确时间,应在数据迁移或写入端改用 BSON Date、int64等合适的表示方式。NewMgoRegistry()带有 mgo 兼容行为。如果项目要求其他类型严格遵循 Driver v2 的标准默认行为,就不应把它视为标准默认 Registry 的等价副本。可以改为只对特定字段使用包装类型或字段级解码逻辑。
备注:内容仅供参考。
