使用MongoDB Go驱动程序将文档解码为具有自定义类型字段的结构体

8

我在go和mongodb方面都是初学者。我尝试使用bson标签将DocumentResult解码为结构体,但对于包装字符串的自定义类型无法正常工作。是否可以在不将字段类型更改为字符串的情况下完成?

    import (
    "context"
    "github.com/mongodb/mongo-go-driver/mongo"
)

type MyDoc struct {
    SomeInt int `bson:"some_int"`
    SomeString string `bson:"some_string,omitempty"`
    CustomType MyType `bson:"custom_type,omitempty"`
}

type MyType string

const myType MyType = "ABCD"

func main() {

    //Connect to db
    client, _ := mongo.Connect(context.Background(), "mongodb://localhost:27017", nil)
    db := client.Database("example_db")
    collection := db.Collection("col")

    //Insert document
    docToInsert := MyDoc{42, "The Answer", myType}
    collection.InsertOne(nil, docToInsert)

    //Retrieve document
    filterDoc := MyDoc{SomeInt: 42}
    resultDoc := &MyDoc{}
    result := collection.FindOne(nil, filterDoc)
    result.Decode(resultDoc)

    println(resultDoc.SomeInt, resultDoc.SomeString, resultDoc.CustomType)

打印结果:"42 The Answer" //"ABCD"缺失

提前致谢。

3个回答

6

我尝试使用bson标签将DocumentResult解码为结构体,但对于包装字符串的自定义类型无法正常工作

使用您当前的MyType,存储在MongoDB中的文档如下:

{
  "_id": ObjectId("..."),
  "some_int": NumberLong("42"),
  "some_string": "The Answer",
  "custom_type": "ABCD"
}

尽管底层类型是字符串,但由于类型包装,使用当前版本的mongo-go-driver(v0.0.12)进行解码可能会很棘手。
然而,如果您想要这样的自定义类型,可以将结构体改为一个嵌入字段。例如:
type MyDoc struct {
    SomeInt    int    `bson:"some_int"`
    SomeString string `bson:"some_string,omitempty"`
    CustomType MyType `bson:"custom_type,omitempty"`
}

type MyType struct {
    Value string `bson:"value,omitempty"`
}

var myType = MyType{Value: "ABCD"}

docToInsert := MyDoc{42, "The Answer", "ABCD"}

insertResult, err := collection.InsertOne(nil, docToInsert)

resultDoc := collection.FindOne(context.Background(), nil)
if err != nil {
    log.Fatal(err)
}
elem := &MyDoc{}
err = resultDoc.Decode(elem)
if err != nil {
    log.Fatal(err)
}
fmt.Println(elem.SomeInt, elem.SomeString, elem.CustomType.Value)
// 42 The Answer ABCD

文档将存储在MongoDB中,如下所示:

{
  "_id": ObjectId("..."),
  "some_int": NumberLong("42"),
  "some_string": "The Answer",
  "custom_type": {
    "value": "ABCD"
  }
}

否则,直接使用string类型即可,因为在数据库中生成的文档与类型包装版本相同。
type MyDoc struct {
    SomeInt    int    `bson:"some_int"`
    SomeString string `bson:"some_string,omitempty"`
    CustomType string `bson:"custom_type,omitempty"`
} 

您可能也会发现 MongoDB数据建模是一个有用的参考资料。


4

前言:现在驱动程序已经自动处理具有string作为其基础类型的自定义类型。本答案早于驱动程序1.x版本,当时这是必要的。


不幸的是,官方mongo go驱动程序的当前状态不支持将string值从BSON解组到Go值,其类型是具有string作为其基础类型的自定义类型。这可能会在未来发生变化,但目前不支持此功能。

处理解码到结构字段的方式实现在bson/decode.go,当前位于第387行

case 0x2:
    str := v.StringValue()
    switch containerType {
    case tString, tEmpty:
        val = reflect.ValueOf(str)
    case tJSONNumber:
        _, err := strconv.ParseFloat(str, 64)
        if err != nil {
            return val, err
        }
        val = reflect.ValueOf(str).Convert(tJSONNumber)

    case tURL:
        u, err := url.Parse(str)
        if err != nil {
            return val, err
        }
        val = reflect.ValueOf(u).Elem()
    default:
        return val, nil
    }

0x02是BSON字符串类型。仅当结构字段的类型为以下任一类型之一时,才尝试将其解码为该结构字段:stringinterface{}json.Numberurl.URL(或指向这些类型的指针)。

不幸的是,在您的自定义类型上实现bson.Unmarshaler也无济于事,因为在结构字段情况下不会进行检查,只有在结构本身实现它时才会进行检查。但是,在结构体本身上实现,您必须使用支持上述列出的类型之一的字段复制结构(或使用map或bson.Document类型)。

这是库方面的严重限制,可以非常容易地解决,因此让我们希望他们能在不久的将来添加此功能的支持。


非常感谢。我通过为结构体的类型指针实现一个UnmarshallBSON函数来解决了这个问题。然后decode函数会调用它,在里面定义了一个类似的结构体,只有一个string,所以我可以将文档解析成它。然后我使用新定义的结构体进行初始化,再进行一些强制类型转换就行了。效果很好。 再次感谢! - amz
这在1.x版本的驱动程序中已不再需要,编码/解码可以直接使用。 - Alexej Kubarev
@AlexejK 谢谢,我会更新答案以反映这一点。 - icza

2

使用Go语言的MongoDB 1.x驱动程序(撰写本文时的最新版本为1.3.1),完全可以对别名类型进行编码和解码。

只需将mongo.Connect调整为与新的1.x API匹配,您的示例现在就可以按预期工作了。

package main

import (
    "context"

    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

type MyDoc struct {
    SomeInt    int    `bson:"some_int"`
    SomeString string `bson:"some_string,omitempty"`
    CustomType MyType `bson:"custom_type,omitempty"`
}

type MyType string

const myType MyType = "ABCD"

func main() {

    // Connect to db
    clientOpts := options.Client().
        ApplyURI("mongodb://localhost/example_db")
    client, _ := mongo.Connect(context.Background(), clientOpts)
    db := client.Database("example_db")
    collection := db.Collection("col")

    // Insert document
    docToInsert := MyDoc{42, "The Answer", myType}
    collection.InsertOne(nil, docToInsert)

    // Retrieve document
    filterDoc := MyDoc{SomeInt: 42}
    resultDoc := &MyDoc{}
    result := collection.FindOne(nil, filterDoc)
    result.Decode(resultDoc)

    println(resultDoc.SomeInt, resultDoc.SomeString, resultDoc.CustomType)
}

这将返回:42 The Answer ABCD,如预期所示。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接