使用 mgo 强制执行类型映射

Enforce a type mapping with mgo

当 _id 成员的类型仅派生自 bson.ObjectId:

时,它不再映射到 ObjectId 类型
import (
    "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
)

type CustomId bson.ObjectId

type Foo struct {
    ID1    CustomId `bson:"_id"` // broken
    ID2    bson.ObjectId         // mapped as expected
}


func main() {
    session, _ := mgo.Dial("127.0.0.1")
    coll := session.DB("mgodemo").C("foocoll")

    doc := Foo{
        CustomId(bson.NewObjectId()),
        bson.NewObjectId(),
    }

    coll.Insert(doc)
}

_id 应该是 ObjectId in Mongo。 但事实证明 string 被选中:

Mongo Shell:

> db.foocoll.findOne()
{ "_id" : "XvMn]K� �\f:�", "id2" : ObjectId("58764d6e5d4be120fa0c3ab1") }  // id2 is OK ...

> typeof db.foocoll.findOne()._id
string  // OOps. Should be ObjectId !

这可能是有意为之,因为 bson.ObjectId 本身是从 string 派生的。但是在这里,这对我们不利。

我们可以告诉 mgo 将 _id 映射到数据库中的 ObjectId 吗?

当你这样做时:

type CustomId bson.ObjectId

您正在创建一个新类型,mgo 包将不再将其视为/识别为 bson.ObjectId(类型 bson.ObjectId 在 bson 包中是 "hardcoded" ).新类型将有 0 个方法。

我会坚持bson.ObjectId。但是,如果您仍然想要自定义 ID 类型,则可以在创建 CustomId 时使用嵌入:嵌入类型 bson.ObjectId 的值,并使用 inline bson 标志作为 ID1 字段:

type CustomId struct {
    bson.ObjectId `bson:"_id"`
}

type Foo struct {
    ID1 CustomId      `bson:",inline"`
    ID2 bson.ObjectId 
}

使用它:

doc := Foo{
    CustomId{bson.NewObjectId()},
    bson.NewObjectId(),
}

这样做的好处是 CustomId 将拥有 bson.ObjectId 拥有的所有方法,并且您可以添加新方法和 "override" 现有方法。

另一种选择是为您的 CustomId 使用接口类型(例如 interface{}),使用它会很多 "simpler":

type CustomId interface{}

type Foo struct {
    ID1 CustomId      `bson:"_id"`
    ID2 bson.ObjectId // mapped as expected
}

使用它:

doc := Foo{
    bson.NewObjectId(),
    bson.NewObjectId(),
}

当然,沿着这条路走下去,你必须使用 type assertion,如果你需要访问 CustomId 的包装 bson.ObjectId

使用Setter and Getter接口来控制mongo中的表示:

type CustomId bson.ObjectId

func (id *CustomId) SetBSON(raw bson.Raw) error {
   var v bson.ObjectId
   err := raw.Unmarshal(&v)
   *id = CustomId(v)
   return err
}
func (id CustomId) GetBSON() (interface{}, error) {
   return bson.ObjectId(id), nil
}