mongodb-go-driver/bson struct to bson.Document encoding

匿名 (未验证) 提交于 2019-12-03 01:35:01

问题:

I'm working with https://github.com/mongodb/mongo-go-driver and currently trying to implement a partial update of such struct

type NoteUpdate struct {     ID        string `json:"id,omitempty" bson:"_id,omitempty"`     Title     string `json:"title" bson:"title,omitempty"`     Content   string `json:"content" bson:"content,omitempty"`     ChangedAt int64  `json:"changed_at" bson:"changed_at"` } 

For instance, if I have

noteUpdate := NoteUpdate{ Title: "New Title" } 

Then I expect that the only "title" field in the stored document will be changed.

I need to write something like

collection.FindOneAndUpdate(context.Background(),     bson.NewDocument(bson.EC.String("_id", noteUpdate.ID)),     // I need to encode non-empty fields here     bson.NewDocument(bson.EC.SubDocument("$set", bson.NewDocument(...))) ) 

The problem is that I don't want to manually encode each non-empty field with bson.EC.String(...) or bson.EC.Int64(...). I tried to use bson.EC.InterfaceErr(...) but got an error

Cannot create element for type *models.NoteUpdate, try using bsoncodec.ConstructElementErr

Unfortunately, there is no such function in bsoncodec. The only way I found is to create wrapper

type SetWrapper struct {     Set interface{} `bson:"$set,omitempty"` } 

And use it like

partialUpdate := &NoteUpdate{     ID: "some-note-id",      Title: "Some new title",  } updateParam := SetWrapper{Set: partialUpdate} collection.FindOneAndUpdate(     context.Background(),     bson.NewDocument(bson.EC.String("_id", noteUpdate.ID)),     updateParam, ) 

It works, but is it possible to achieve the same with bson/bsoncodec document builders ?

UPD. The full context of my question: I wrote the REST endpoint for partially updating "Note" documents(stored in MongoDB). Code that I have now:

var noteUpdate models.NoteUpdate ctx.BindJSON(&noteUpdate)     //omit validation and errors handling updateParams := services.SetWrapper{Set: noteUpdate} res := collection.FindOneAndUpdate( context.Background(), bson.NewDocument(bson.EC.String("_id", noteUpdate.ID)),     updateParams,     findopt.OptReturnDocument(option.After), ) 

Code that I want to have

var noteUpdate models.NoteUpdate ctx.BindJSON(&noteUpdate)     //omit validation and errors handling res := collection.FindOneAndUpdate(     context.Background(),     bson.NewDocument(bson.EC.String("_id", noteUpdate.ID)),     bson.NewDocument(         //bsoncodec.ConstructElement doesn't exists         bsoncodec.ConstructElement("$set", &noteUpdate)),         ),     findopt.OptReturnDocument(option.After), ) 

Code that I don't want to have

var noteUpdate models.NoteUpdate ctx.BindJSON(&noteUpdate) //omit validation and errors handling bsonNote := bson.NewDocument() if noteUpdate.Title != "" {     bsonNote.Append(bson.EC.String("title", noteUpdate.Title)) } if noteUpdate.Content != "" {     bsonNote.Append(bson.EC.String("content", noteUpdate.Content)) } //..setting the rest of the fields... res := collection.FindOneAndUpdate(     context.Background(),     bson.NewDocument(bson.EC.String("_id", noteUpdate.ID)),     bson.NewDocument(bson.EC.SubDocument("$set", bsonNote)),     findopt.OptReturnDocument(option.After), ) 

So, the precise question is - is there any way to build *bson.Document dynamically based on bson tags(without predefined wrappers like my SetWrapper)?

回答1:

Unfortunately this is currently not supported.

You may create a helper function which "converts" a struct value to a bson.Document like this:

func toDoc(v interface{}) (doc *bson.Document, err error) {     data, err := bson.Marshal(v)     if err != nil {         return     }      err = bson.Unmarshal(data, &doc)     return } 

Then it can be used like this:

partialUpdate := &NoteUpdate{     Title: "Some new title", }  doc, err := toDoc(partialUpdate) // check error  res := c.FindOneAndUpdate(     context.Background(),     bson.NewDocument(bson.EC.String("_id", "some-note-id")),     bson.NewDocument(bson.EC.SubDocument("$set", doc)), ) 

Hopefully ElementConstructor.Interface() will improve in the future and allow passing struct values or pointers to struct values directly.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!