mongodb-go-driver / bson结构转换为bson.Document编码

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

doutuo7156
doutuo7156 您添加的内容并不能真正解释任何内容。不要显示“我想要类似的东西”,而是显示您实际尝试使用的代码,然后显示其问题。您现在显示的只是$set“,bson.NewDocument(...),而您正在忽略的...部分实际上似乎是问题的实际重要部分。
接近 2 年之前 回复
doucao1066
doucao1066 我添加了$set操作期望的示例。我只想更新存储文档的某些字段,而不是完全替换它。
接近 2 年之前 回复
duanbin3021
duanbin3021 “问题是我不想手动对每个非空字段进行编码”-您能否在此问题的确切含义中提供更多背景信息?$set操作只能触摸在其下显式命名的字段。完全“省略”$set仅使用指定的属性即可有效地“覆盖”整个现有文档。现代API甚至使用*Replace*变体对此进行了细分,为代码清晰起见,该变体专门禁止在此上下文中使用$set和其他原子运算符。您所期望的例子将更加清晰
接近 2 年之前 回复

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.

Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问