Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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)?

like image 239
Danilа Maslennikov Avatar asked Nov 01 '18 22:11

Danilа Maslennikov


People also ask

How do you Unmarshal BSON?

You can unmarshal BSON documents by using the Decode() method on the result of the FindOne method or any *mongo.

Does MongoDB use BSON to represent document structure?

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.

What is BSON D and BSON M?

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.

How do you parse BSON?

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.


1 Answers

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.

like image 107
icza Avatar answered Oct 12 '22 14:10

icza