mongodb-go-driver/bson struct 到 bson.Document 编码

Dan*_*kov 10 go mongodb bson mongo-go

我正在与https://github.com/mongodb/mongo-go-driver 合作,目前正在尝试实现此类结构的部分更新

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"`
}
Run Code Online (Sandbox Code Playgroud)

例如,如果我有

noteUpdate := NoteUpdate{ Title: "New Title" }
Run Code Online (Sandbox Code Playgroud)

然后我希望存储文档中唯一的“标题”字段将被更改。

我需要写一些类似的东西

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(...)))
)
Run Code Online (Sandbox Code Playgroud)

问题是我不想用bson.EC.String(...)or手动编码每个非空字段bson.EC.Int64(...)。我尝试使用bson.EC.InterfaceErr(...)但出现错误

无法为 *models.NoteUpdate 类型创建元素,请尝试使用 bsoncodec.ConstructElementErr

不幸的是,bsoncodec 中没有这样的功能。我发现的唯一方法是创建包装器

type SetWrapper struct {
    Set interface{} `bson:"$set,omitempty"`
}
Run Code Online (Sandbox Code Playgroud)

并使用它

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,
)
Run Code Online (Sandbox Code Playgroud)

它有效,但是是否可以使用 bson/bsoncodec 文档构建器实现相同的效果?

更新。我的问题的完整上下文:我编写了 REST 端点,用于部分更新“Note”文档(存储在 MongoDB 中)。我现在拥有的代码:

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),
)
Run Code Online (Sandbox Code Playgroud)

我想要的代码

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),
)
Run Code Online (Sandbox Code Playgroud)

代码,我希望有

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),
)
Run Code Online (Sandbox Code Playgroud)

所以,确切的问题是 - 有没有办法基于bson标签动态构建 *bson.Document (没有像我的 SetWrapper 这样的预定义包装器)?

icz*_*cza 15

不幸的是,目前不支持。

您可以创建一个辅助函数,它将结构值“转换”为bson.Document这样的:

func toDoc(v interface{}) (doc *bson.Document, err error) {
    data, err := bson.Marshal(v)
    if err != nil {
        return
    }

    err = bson.Unmarshal(data, &doc)
    return
}
Run Code Online (Sandbox Code Playgroud)

然后它可以像这样使用:

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)),
)
Run Code Online (Sandbox Code Playgroud)

希望ElementConstructor.Interface()将来会改进并允许直接传递结构值或指向结构值的指针。