PAYATHON 2026

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 的等价副本。可以改为只对特定字段使用包装类型或字段级解码逻辑。

备注:内容仅供参考。