13

The update function of mongo-go-driver can be called like this.

filter := bson.D{"username", username}
update := bson.D{{"$set",
    bson.D{
        {"name", person.Name},
    },
}}
result, err := collection.UpdateOne(ctx, filter, update)
type Person struct {
    ID       primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
    Username string             `json:"username,omitempty" bson:"username,omitempty"`
    Name     string             `json:"name,omitempty" bson:"name,omitempty"`
}

But, I need to call the update function using the person struct, without mentioning every field of person struct like this.

filter := bson.D{"username", username}
update := bson.D{{"$set", <<how to convert person struct to bson document?>>}}
result, err := collection.UpdateOne(ctx, filter, update)

How can I convert the person struct to bson document?

sumedhe
  • 934
  • 1
  • 13
  • 30

4 Answers4

8

ReplaceOne I think is what you're after:

        // Use it's ID to replace
        filter := bson.M{"_id": existing.ID}
        // Create a replacement object using the existing object
        replacementObj := existing
        replacementObj.SomeFieldToChange = "new-replacement-object"
        updateResult, err := coll.ReplaceOne(context.Background(), filter, replacementObj)
        assertNotErr(t, err)
        assertEquals(t, 1, int(updateResult.ModifiedCount))

A note that ErrNotFound is no longer thrown as it was in mgo - you have to check the Modified/Upserted count.

TopherGopher
  • 655
  • 11
  • 21
  • 1
    How does replace and update differ? Does reindexing happen if you use replace? – Chris Nov 20 '20 at 00:24
  • 1
    Replace is for when you have the whole object/document that you're dropping into place whereas Update is used to update just part of a document, e.g. $inc (increment) some integer, $set an arbitrary value, etc. Does that make sense? – TopherGopher Nov 20 '20 at 07:21
  • That makes sense. Do you know if it does a remove+add or does it modify the document in place? – Chris Nov 20 '20 at 20:33
  • 1
    Effectively modifies in place, just overwriting the existing data and dropping the new data in. Anything that was there is lost. – TopherGopher Nov 20 '20 at 23:56
7

You could do something like this:

func Update(person Person) error {
  pByte, err := bson.Marshal(person)
  if err != nil {
    return err
  }

  var update bson.M
  err = bson.Unmarshal(pByte, &update)
  if err != nil {
    return
  }

  // NOTE: filter and ctx(Context) should be already defined
  _, err = collection.UpdateOne(ctx, filter, bson.D{{Key: "$set", Value: update}})
  if err != nil {
    return err
  }
  return nil
}
lakex
  • 105
  • 1
  • 1
  • How we can get the "filter" object ? Please add the filter code also – Muhammad Tariq Jan 24 '21 at 13:44
  • @MuhammadTariq i know its late but setting filter is easy. filter := bson.D{{"_id", id}} I request you to read the official document before asking question. https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Collection.UpdateOne – Anish Yadav Sep 05 '21 at 13:13
  • wouldn’t this throw a error about immutable _id since _id is in the update object ? – Someone Special Sep 09 '22 at 12:49
  • 1
    It dosen't work, got mongo.MarshalError `cannot transform type bson.D to a BSON Document: WriteArray can only write a Array while positioned on a Element or Value but is positioned on a TopLevel`. It seems that the `Value` must be a bson.D – van Apr 01 '23 at 12:21
0

how about marshalling the person struct to bson?

package main

import (
        "fmt"

        "labix.org/v2/mgo/bson"
)

type person struct {
        ID       string `json:"_id,omitempty" bson:"_id,omitempty"`
        Username string `json:"username,omitempty" bson:"username,omitempty"`
        Name     string `json:"name,omitempty" bson:"name,omitempty"`
}

func main() {
        p := person{
                ID:       "id",
                Username: "uname",
                Name:     "name",
        }
        var (
                bm  []byte
                err error
        )
        if bm, err = bson.Marshal(p); err != nil {
                panic(fmt.Errorf("can't marshal:%s", err))
        }
        update := bson.D{{"$set", bm}}
        fmt.Printf("update is:%q\n", update)
}

run:

 ./sobson
update is:[{"$set" "4\x00\x00\x00\x02_id\x00\x03\x00\x00\x00id\x00\x02username\x00\x06\x00\x00\x00uname\x00\x02name\x00\x05\x00\x00\x00name\x00\x00"}]
ramrunner
  • 1,362
  • 10
  • 20
  • I tried this method. But, I'm getting this error: multiple write errors: [{write errors: [{Modifiers operate on fields but we found type binData instead. For example: {$mod: {: ...}} not {$set: BinData(0, 6D0000000275736572000C00000073756D6564686566736673000266697273746E616D65000A00000053756D65647373736400026C6173746E616D65000B0000004469737361...)}}]}, {}] ``` – sumedhe Apr 30 '19 at 19:35
  • There's no need to marshal the object to bson - ReplaceOne should* take care of it – TopherGopher Dec 04 '19 at 21:48
  • This works beautifully - I take a struct from the request body, marshal it into bson, and unmarshal it. This will remove all the empty fields, so your document won't update with several blank fields, thus only updating the exact fields your user intends to update without allowing them to update other fields they should not be able to access :) – Aphix Feb 03 '23 at 01:35
0

I would like to propose more straightforward approach that involves some boilerplate but results an accurate and extensible code.

// define consts for column names to avoid typos
const (
    ColPersonUsername = "username"
    ColPersonName     = "name"
)

type Person struct {
    ID       primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
    Username string             `json:"username,omitempty" bson:"username,omitempty"`
    Name     string             `json:"name,omitempty" bson:"name,omitempty"`
}

// create helper methods and functions for BSON generation 
func (p Person) encodeBSON() interface{} {
    return bson.M{
        ColPersonUsername: p.Username,
        ColPersonName:     p.Name,
    }
}

func filterByID(id interface{}) interface{} {
    return bson.M{
        "_id": id,
    }
}

Then the update function can be something like this:

func (s *Store) UpdatePerson(ctx context.Context, person Person) error {
    _, err := s.collection.UpdateOne(ctx, 
        filterByID(person.ID), 
        bson.M{"$set": person.encodeBSON()})
    if err != nil {
        return err
    }
    return nil
}
igorushi
  • 1,855
  • 21
  • 19