Mongo-go-driver使用通用接口解码返回Key-Value数组而非结构体问题
问题原因
你现在的写法核心问题是Decode传入的目标参数类型是*interface{},Mongo-go-driver的解码器无法识别interface{}底层存储的实际结构体类型,会默认将BSON文档解码为bson.D类型(也就是你看到的Key-Value数组格式)。
你预先存在models里的结构体值在赋值给result后,result的静态类型就是interface{},取地址后是*interface{}类型,解码器不会尝试探测interface{}里的原有类型,直接按默认规则解码,才会出现不符合预期的结果。
可行解决方案
这里提供两种可落地的实现方案:
方案1:通过构造函数预注册模型(推荐,无反射开销)
修改models的存储类型为返回结构体指针的构造函数,每次查询时调用构造函数生成全新的结构体实例指针供解码使用,代码如下:
var ( models map[string]func() interface{} ) func init() { models = make(map[string]func() interface{}) // 注册各集合对应的构造函数,返回对应结构体的指针 models["Overview"] = func() interface{} { return &Overview{} } models["Earnings"] = func() interface{} { return &Earnings{} } // 其他模型注册逻辑... } func GetDbData(collection string, db *mongo.Database, filter bson.D) (interface{}, error) { // 调用构造函数生成新的结构体指针实例 result := models[collection]() res := db.Collection(collection).FindOne(context.TODO(), filter) err := res.Decode(result) if err != nil { return nil, err } // 返回的result已经是填充好数据的结构体指针,也可以根据需要返回值类型 return result, nil }
方案2:通过反射动态创建实例
如果不想写大量构造函数,也可以预先存储模型的反射类型,查询时动态生成实例:
import "reflect" var ( models map[string]reflect.Type ) func init() { models = make(map[string]reflect.Type) // 注册各集合对应的结构体类型 models["Overview"] = reflect.TypeOf(Overview{}) models["Earnings"] = reflect.TypeOf(Earnings{}) // 其他模型注册逻辑... } func GetDbData(collection string, db *mongo.Database, filter bson.D) (interface{}, error) { // 动态创建对应类型的指针实例 result := reflect.New(models[collection]).Interface() res := db.Collection(collection).FindOne(context.TODO(), filter) err := res.Decode(result) if err != nil { return nil, err } // 如果需要返回值类型而非指针,可以用 reflect.ValueOf(result).Elem().Interface() return result, nil }
补充注意事项
- 所有业务结构体的字段必须首字母大写(公开可导出),否则解码器无法给字段赋值
- 建议给结构体字段补充
bson标签,和MongoDB的字段名显式对应,避免默认字段名转换逻辑不符合预期:type Overview struct { Symbol string `json:"Symbol,omitempty" bson:"Symbol,omitempty"` AssetType string `json:"AssetType,omitempty" bson:"AssetType,omitempty"` // 其他字段同理 }
内容的提问来源于stack exchange,提问作者sorohimm
相关产品推荐
相关产品推荐

