
本文介绍如何通过类型别名配合 `setbson`/`getbson` 方法,实现对结构体指针字段(如 `*tool`)的差异化 bson 序列化,避免默认内联嵌套,转而仅存储 id 等精简引用。
在使用 mgo 驱动操作 MongoDB 时,结构体字段无论是否为指针(如 Tool vs *Tool),只要其底层类型相同,默认都会被统一内联序列化为嵌套 BSON 文档。这在需要区分“值内联”与“引用存储”(例如仅保存 _id 字符串)的场景下会造成困扰——因为 SetBSON/GetBSON 接口作用于类型本身,而非指针或非指针形态。
根本原因:mgo 的 marshalling 机制基于 Go 类型系统,*Tool 和 Tool 是两种不同类型,但 *Tool 的 SetBSON 方法若未显式定义,将无法触发自定义逻辑;而直接为 Tool 实现 SetBSON 又会同时影响值和指针两种使用方式,失去区分能力。
✅ 推荐解决方案:引入语义化类型别名
通过定义一个新类型(如 SelectiveTool)作为 Tool 的别名,并为其指针实现专属的 SetBSON 和 GetBSON,即可精准控制指针字段的序列化行为:
type Tool struct {
ID bson.ObjectId `bson:"_id,omitempty"`
Name string `bson:"name"`
// ... other fields
}
// SelectiveTool 专用于需“按引用序列化”的场景(如关联 ID)
type SelectiveTool Tool
// SetBSON 控制 *SelectiveTool 如何从 BSON 解码(例如只读取 _id 并查库/构造)
func (st *SelectiveTool) SetBSON(raw bson.Raw) error {
var id bson.ObjectId
if err := raw.Unmarshal(&struct{ ID bson.ObjectId }{ID: id}); err != nil {
return err
}
// 此处可按需加载完整 Tool 实例,或仅保留 ID 供后续使用
*st = SelectiveTool{ID: id}
return nil
}
// GetBSON 控制 *SelectiveTool 如何编码为 BSON(仅输出 _id)
func (st *SelectiveTool) GetBSON() (interface{}, error) {
if st == nil {
return nil, nil
}
return struct{ ID bson.ObjectId }{ID: st.ID}, nil
}
// 使用示例
type Order struct {
ID bson.ObjectId `bson:"_id,omitempty"`
Item Tool `bson:"item"`
AssociatedItem *SelectiveTool `bson:"associated_item"` // ✅ 仅存 ID,不内联完整 Tool
}⚠️ 注意事项:
- SelectiveTool 必须是 Tool 的命名类型别名(type SelectiveTool Tool),而非类型别名(type SelectiveTool = Tool),否则方法集不继承;
- 若需反向查库填充完整 Tool,应在 SetBSON 中调用业务逻辑(注意避免循环依赖或 DB 连接泄漏);
- GetBSON 返回 nil 表示空指针不写入字段,符合 MongoDB 最佳实践;
- 升级至 mongo-go-driver 后,可通过 MarshalBSON/UnmarshalBSON 或自定义 bson.Marshaler 接口获得更灵活控制,但本方案在 mgo 生态中简洁可靠。
该方案以最小侵入性实现了语义分离:Tool 代表“内联值”,*SelectiveTool 代表“轻量引用”,清晰表达设计意图,且完全兼容 mgo 的 marshalling 生命周期。










