(извините, этот вопрос оказался дольше, чем я думал ...)
Я использую Go и MongoDB с драйвером mgo.Я пытаюсь сохранить и извлечь различные структуры (реализуя общий интерфейс) в одной коллекции MongoDB.Я пришел из мира Java (где это очень легко сделать с помощью Spring без буквального конфигурирования), и мне трудно делать что-то подобное с Go.Я прочитал каждую последнюю связанную статью или пост или вопрос StackExchange, который я мог найти, но все же я не нашел полного решения.Это включает в себя:
Вот упрощенная настройка, которую я использую для тестирования.Предположим, две структуры 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")
}
Теперь я могу легко сохранить экземпляр S1
или S2
, используя mgo.Collection.Insert()
, но для правильного заполнения значения, например, mgo.Collection.Find().One()
, мне нужно передать указатель насуществующее значение S1
или S2
, что означает, что я уже знаю тип объекта, который хочу прочитать !!Я хочу иметь возможность получить документ из коллекции MongoDB, не зная, является ли это S1
, или S2
, или фактически любым объектом, который реализует I
.
Вот где я такдалеко: вместо непосредственного сохранения объекта, который я хочу сохранить, я сохраняю структуру Wrapper
, которая содержит идентификатор MongoDB, идентификатор типа и фактическое значение.Идентификатор типа представляет собой объединение packageName + "."+ typeName , и он используется для поиска типа в реестре типов, поскольку отсутствует собственный механизм для сопоставления имени типа с объектом Type в Go.Это означает, что мне нужно зарегистрировать типы, которые я хочу сохранить и восстановить, но я мог бы жить с этим.Вот как это происходит:
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
}
Это частично работает, поскольку возвращаемый объект набирается правильно, но я не могу понять, как заполнить значение pt
с помощьюданные, извлеченные из MongoDB, которые хранятся в 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