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(¬eUpdate)
//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(¬eUpdate)
//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", ¬eUpdate)),
),
findopt.OptReturnDocument(option.After),
)
Code that I don't want to have
var noteUpdate models.NoteUpdate
ctx.BindJSON(¬eUpdate)
//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)?
You can unmarshal BSON documents by using the Decode() method on the result of the FindOne method or any *mongo.
Does MongoDB use BSON, or JSON? MongoDB stores data in BSON format both internally, and over the network, but that doesn't mean you can't think of MongoDB as a JSON database. Anything you can represent in JSON can be natively stored in MongoDB, and retrieved just as easily in JSON.
As per the documentation bson. D should be used if the order of elements matters and bson. M should be used otherwise. D is an ordered representation of a BSON document. This type should be used when the order of the elements matters, such as MongoDB command documents.
BSON documents are lazily parsed as necessary. To begin parsing a BSON document, use one of the provided Libbson functions to create a new bson_t from existing data such as bson_new_from_data(). This will make a copy of the data so that additional mutations may occur to the BSON document.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With