How use update function of mongo-go-driver using struct
Asked Answered
S

4

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?

Storytelling answered 29/4, 2019 at 13:43 Comment(1)
Where you able to solve this?Brambly
B
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.

Bindle answered 4/12, 2019 at 21:47 Comment(4)
How does replace and update differ? Does reindexing happen if you use replace?Osteoblast
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?Bindle
That makes sense. Do you know if it does a remove+add or does it modify the document in place?Osteoblast
Effectively modifies in place, just overwriting the existing data and dropping the new data in. Anything that was there is lost.Bindle
O
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
}
Oro answered 20/12, 2019 at 11:50 Comment(4)
How we can get the "filter" object ? Please add the filter code alsoSpleen
@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. pkg.go.dev/go.mongodb.org/mongo-driver/…Uncommonly
wouldn’t this throw a error about immutable _id since _id is in the update object ?Fimbriate
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.DHouseholder
S
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"}]
Schneider answered 29/4, 2019 at 16:57 Comment(3)
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: {<field>: ...}} not {$set: BinData(0, 6D0000000275736572000C00000073756D6564686566736673000266697273746E616D65000A00000053756D65647373736400026C6173746E616D65000B0000004469737361...)}}]}, {<nil>}] ```Storytelling
There's no need to marshal the object to bson - ReplaceOne should* take care of itBindle
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 :)Hanker
M
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
}
Melbamelborn answered 2/5, 2023 at 16:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.