包github.com/google/uuid
没有实现bson.ValueMarshaler接口来将自身编组为MongoDB UUID,这就是它不起作用的原因.
我们可以注册自己的编解码器,将其编码为MongoDB UUID或从MongoDB UUID进行解码.请看下面的演示:
package main
import (
"context"
"fmt"
"log"
"reflect"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Person struct {
Name string `bson:"name"`
Sign uuid.UUID `bson:"sign"`
}
func main() {
tUUID := reflect.TypeOf(uuid.Nil)
bson.DefaultRegistry.RegisterTypeEncoder(tUUID, bsoncodec.ValueEncoderFunc(func(ec bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
// borrowed from https://github.com/mongodb/mongo-go-driver/blob/89c7e1d1d9a2954472fe852baf27ecca9dbf9bbf/bson/bsoncodec/default_value_encoders.go#L684-L691
if !val.IsValid() || val.Type() != tUUID {
return bsoncodec.ValueEncoderError{Name: "UUIDEncodeValue", Types: []reflect.Type{tUUID}, Received: val}
}
uuid := val.Interface().(uuid.UUID)
return vw.WriteBinaryWithSubtype(uuid[:], bson.TypeBinaryUUID)
}))
bson.DefaultRegistry.RegisterTypeDecoder(tUUID, bsoncodec.ValueDecoderFunc(func(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
// borrowed from https://github.com/mongodb/mongo-go-driver/blob/89c7e1d1d9a2954472fe852baf27ecca9dbf9bbf/bson/bsoncodec/default_value_decoders.go#L694-L706
if !val.CanSet() || val.Type() != tUUID {
return bsoncodec.ValueDecoderError{Name: "UUIDDecodeValue", Types: []reflect.Type{tUUID}, Received: val}
}
var data []byte
var subtype byte
var err error
switch vrType := vr.Type(); vrType {
case bson.TypeBinary:
data, subtype, err = vr.ReadBinary()
case bson.TypeNull:
err = vr.ReadNull()
case bson.TypeUndefined:
err = vr.ReadUndefined()
default:
err = fmt.Errorf("cannot decode %v into a Binary", vrType)
}
if err != nil {
return err
}
if subtype != bson.TypeBinaryUUID {
return fmt.Errorf("cannot decode subtype %v into a UUID", subtype)
}
val.Set(reflect.ValueOf(uuid.UUID(data)))
return nil
}))
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
log.Fatal(err)
}
defer client.Disconnect(context.Background())
db := client.Database("mydatabase")
collection := db.Collection("people")
signUUID := uuid.New()
person := Person{
Name: "John",
Sign: signUUID,
}
if _, err = collection.InsertOne(context.Background(), person); err != nil {
log.Fatal(err)
}
var p Person
if err := collection.FindOne(context.Background(),
bson.M{"sign": signUUID},
).Decode(&p); err != nil {
log.Fatal(err)
}
log.Printf("%+v\n", p)
}
请看这里的讨论:Add support for mongodb UUID type.
为了完整起见,下面显示了实现bson.ValueMarshaler
和bson.ValueUnmarshaler
接口的方法:
package main
import (
"context"
"errors"
"fmt"
"log"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
type myUUID struct {
uuid.UUID
}
var (
_ bson.ValueMarshaler = (*myUUID)(nil)
_ bson.ValueUnmarshaler = (*myUUID)(nil)
)
func (u myUUID) MarshalBSONValue() (bsontype.Type, []byte, error) {
return bson.TypeBinary, bsoncore.AppendBinary(nil, bson.TypeBinaryUUID, []byte(u.UUID[:])), nil
}
func (u *myUUID) UnmarshalBSONValue(typ bsontype.Type, value []byte) error {
if typ != bson.TypeBinary {
return fmt.Errorf("cannot unmarshal %v into a Binary", typ)
}
subtype, bin, rem, ok := bsoncore.ReadBinary(value)
if subtype != bson.TypeBinaryUUID {
return fmt.Errorf("cannot unmarshal binary subtype %v into a UUID", subtype)
}
if len(rem) > 0 {
return fmt.Errorf("value has extra data: %v", rem)
}
if !ok {
return errors.New("value does not have enough bytes")
}
*u = myUUID{UUID: uuid.UUID(bin)}
return nil
}
type Person struct {
Name string `bson:"name"`
Sign myUUID `bson:"sign"`
}
func main() {
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
log.Fatal(err)
}
defer client.Disconnect(context.Background())
db := client.Database("mydatabase")
collection := db.Collection("people")
signUUID := myUUID{UUID: uuid.New()}
person := Person{
Name: "John",
Sign: signUUID,
}
if _, err = collection.InsertOne(context.Background(), person); err != nil {
log.Fatal(err)
}
var p Person
if err := collection.FindOne(context.Background(),
bson.M{"sign": signUUID},
).Decode(&p); err != nil {
log.Fatal(err)
}
log.Printf("%+v\n", p)
}