
本文介绍在使用 mgo 驱动时,如何针对结构体指针(如 `*tool`)实现与值类型(如 `tool`)不同的 bson 编码逻辑,例如仅存储 id 而非完整嵌入文档。
在 MongoDB 的 Go 生态中,mgo(尽管已归档,但仍在许多遗留项目中广泛使用)默认将结构体值和其指针均以相同方式内联序列化为 BSON 文档。这意味着如下定义:
type Tool struct {
ID bson.ObjectId `bson:"_id,omitempty"`
Name string `bson:"name"`
}
type Order struct {
Item Tool `bson:"item"`
AssociatedItem *Tool `bson:"associated_item"`
}会导致 AssociatedItem 字段仍被完整嵌入为子文档(如 { "name": "wrench" }),而非我们期望的轻量引用(如仅 { "_id": "..." })。问题根源在于:mgo 的 GetBSON() / SetBSON() 接口作用于类型本身,无法区分 Tool 和 *Tool —— 因为指针类型不会自动继承原类型的 BSON 方法。
✅ 推荐方案:引入语义化包装类型
最清晰、类型安全且符合 Go 惯用法的解法是为需要特殊序列化的指针场景定义独立类型,并为其显式实现 GetBSON() 和 SetBSON():
// SelectiveTool 表示“按需选择性序列化”的 Tool 引用
// 仅序列化 ID(或其他标识字段),不嵌入完整结构
type SelectiveTool Tool
// GetBSON 实现自定义编码:只输出 _id 字段
func (st *SelectiveTool) GetBSON() (interface{}, error) {
if st == nil {
return nil, nil
}
// 仅返回 ObjectId,作为引用
return bson.M{"_id": (*Tool)(st).ID}, nil
}
// SetBSON 实现反序列化:从 {_id: ...} 构造 SelectiveTool
func (st *SelectiveTool) SetBSON(raw bson.Raw) error {
var m bson.M
if err := raw.Unmarshal(&m); err != nil {
return err
}
if id, ok := m["_id"]; ok {
// 假设 Tool.ID 是 bson.ObjectId 类型
if oid, ok := id.(bson.ObjectId); ok {
*st = SelectiveTool(Tool{ID: oid})
return nil
}
}
return errors.New("invalid SelectiveTool BSON: missing or invalid _id")
}随后,在业务结构体中使用该新类型替代原始指针:
type Order struct {
ID bson.ObjectId `bson:"_id,omitempty"`
Item Tool `bson:"item"`
AssociatedItem *SelectiveTool `bson:"associated_item"` // ← 关键变更
}这样,当 mgo.Marshal() 处理 Order 时:
- Item 仍按 Tool 的默认规则(或其 GetBSON)完整序列化;
- AssociatedItem 因类型为 *SelectiveTool,将触发我们定制的 GetBSON(),最终生成类似 { "associated_item": { "_id": "507f1f77bcf86cd799439011" } } 的 BSON。
⚠️ 注意事项
- 不可复用 Tool 的 GetBSON:若 Tool 已实现 GetBSON(),SelectiveTool 不会自动继承它;必须显式重写以满足引用语义。
- nil 安全性:GetBSON() 中需检查 st == nil,避免 panic;SetBSON() 应妥善处理空/无效输入。
- 反序列化一致性:SetBSON() 应尽可能还原为可用状态(如仅恢复 ID),后续可通过 ID 查询完整 Tool。
- 替代方案对比:虽然可尝试用 bson:",inline" + 自定义字段标签等 hack 方式,但类型级隔离更易维护、IDE 友好,且能规避反射歧义。
通过这种「语义类型分离」策略,你能在保持代码清晰的同时,精准控制每种使用场景下的 MongoDB 数据形态 —— 这正是 Go 类型系统赋能数据持久化设计的典型实践。










