【发布时间】:2019-02-26 00:55:41
【问题描述】:
(对不起,这个问题比我想象的要长……)
我将 Go 和 MongoDB 与 mgo 驱动程序一起使用。我试图在同一个 MongoDB 集合中持久化和检索不同的结构(实现一个通用接口)。我来自 Java 世界(使用 Spring 很容易做到这一点,几乎没有配置),我很难用 Go 做类似的事情。 我已经阅读了我能找到的所有相关文章或帖子或 StackExchange 问题,但我仍然没有找到完整的解决方案。这包括:
- Unstructured MongoDB collections with mgo
- How do you create a new instance of a struct from its type at run time in Go?
- Golang reflect: Get Type representation from name?
这是我用于测试的简化设置。假设有两个结构S1 和S2,实现了一个公共接口I。 S2 有一个 S1 类型的隐式字段,这意味着结构方面的 S2 嵌入了 S1 值,而类型方面的 S2 实现 I。
type I interface {
f1()
}
type S1 struct {
X int
}
type S2 struct {
S1
Y int
}
func (*S1) f1() {
fmt.Println("f1")
}
现在我可以使用mgo.Collection.Insert() 轻松保存S1 或S2 的实例,但是要使用mgo.Collection.Find().One() 正确填充值,我需要将指针传递给S1 的现有值或S2,表示我已经知道我要读取的对象的类型!!
我希望能够在不知道它是S1、S2,或者实际上是实现I 的任何对象的情况下从MongoDB 集合中检索文档。
到目前为止,这里是:我没有直接保存我想要持久化的对象,而是保存了一个 Wrapper 结构,它包含 MongoDB id、类型的标识符和实际值。类型标识符是 packageName + "." 的串联。 + typeName,它用于在类型注册表中查找类型,因为在 Go 中没有将类型名称映射到 Type 对象的本地机制。这意味着我需要注册我希望能够持久和检索的类型,但我可以接受。事情是这样的:
typeregistry.Register(reflect.TypeOf((*S1)(nil)).Elem())
typeregistry.Register(reflect.TypeOf((*S2)(nil)).Elem())
这是类型注册表的代码:
var types map[string]reflect.Type
func init() {
types = make(map[string]reflect.Type)
}
func Register(t reflect.Type) {
key := GetKey(t)
types[key] = t
}
func GetKey(t reflect.Type) string {
key := t.PkgPath() + "." + t.Name()
return key
}
func GetType(key string) reflect.Type {
t := types[key]
return t
}
保存对象的代码非常简单:
func save(coll *mgo.Collection, s I) (bson.ObjectId, error) {
t := reflect.TypeOf(s)
wrapper := Wrapper{
Id: bson.NewObjectId(),
TypeKey: typeregistry.GetKey(t),
Val: s,
}
return wrapper.Id, coll.Insert(wrapper)
}
检索对象的代码有点棘手:
func getById(coll *mgo.Collection, id interface{}) (*I, error) {
// read wrapper
wrapper := Wrapper{}
err := coll.Find(bson.M{"_id": id}).One(&wrapper)
if err != nil {
return nil, err
}
// obtain Type from registry
t := typeregistry.GetType(wrapper.TypeKey)
// get a pointer to a new value of this type
pt := reflect.New(t)
// FIXME populate value using wrapper.Val (type bson.M)
// HOW ???
// return the value as *I
i := pt.Elem().Interface().(I)
return &i, nil
}
当返回的对象键入正确时,这部分起作用,但我不知道如何使用从 MongoDB 检索到的数据填充值 pt,该数据存储在 wrapper.Val 中作为 bson.M。
我尝试了以下方法,但它不起作用:
m := wrapper.Val.(bson.M)
bsonBytes, _ := bson.Marshal(m)
bson.Unmarshal(bsonBytes, pt)
所以基本上剩下的问题是:如何从bson.M 值填充未知结构?我确信必须有一个简单的解决方案......
提前感谢您的帮助。
这是一个包含所有代码的 Github 要点:https://gist.github.com/ogerardin/5aa272f69563475ba9d7b3194b12ae57
【问题讨论】:
标签: mongodb go polymorphism mgo