O problema é que os codecs bson atuais não suportam codificação/decodificação
string em / de null . Uma maneira de lidar com isso é criar um decodificador personalizado para
string tipo no qual lidamos com null valores:usamos apenas a string vazia (e, mais importante, não relatamos erros). Os decodificadores personalizados são descritos pelo tipo
bsoncodec.ValueDecoder . Eles podem ser registrados em um bsoncodec.Registry , usando um bsoncodec.RegistryBuilder por exemplo. Os registros podem ser definidos/aplicados em vários níveis, até mesmo para um
mongo.Client inteiro , ou para um mongo.Database ou apenas para um mongo.Collection , ao adquiri-los, como parte de suas opções, por exemplo. options.ClientOptions.SetRegistry() . Primeiro vamos ver como podemos fazer isso para
string , e a seguir veremos como melhorar/generalizar a solução para qualquer tipo. 1. Manipulando null cordas
Primeiramente, vamos criar um decodificador de string personalizado que pode transformar um
null em uma string (n vazia):import (
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
type nullawareStrDecoder struct{}
func (nullawareStrDecoder) DecodeValue(dctx bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.CanSet() || val.Kind() != reflect.String {
return errors.New("bad type or not settable")
}
var str string
var err error
switch vr.Type() {
case bsontype.String:
if str, err = vr.ReadString(); err != nil {
return err
}
case bsontype.Null: // THIS IS THE MISSING PIECE TO HANDLE NULL!
if err = vr.ReadNull(); err != nil {
return err
}
default:
return fmt.Errorf("cannot decode %v into a string type", vr.Type())
}
val.SetString(str)
return nil
}
OK, e agora vamos ver como utilizar este decodificador de string personalizado para um
mongo.Client :clientOpts := options.Client().
ApplyURI("mongodb://localhost:27017/").
SetRegistry(
bson.NewRegistryBuilder().
RegisterDecoder(reflect.TypeOf(""), nullawareStrDecoder{}).
Build(),
)
client, err := mongo.Connect(ctx, clientOpts)
A partir de agora, usando este
client , sempre que você decodificar resultados em string valores, este registrado nullawareStrDecoder decodificador será chamado para lidar com a conversão, que aceita bson null valores e define a string vazia Go "" . Mas podemos fazer melhor... Continue lendo...
2. Manipulando null valores de qualquer tipo:decodificador de reconhecimento nulo "tipo neutro"
Uma maneira seria criar um decodificador personalizado separado e registrá-lo para cada tipo que desejamos manipular. Isso parece ser muito trabalho.
O que podemos (e devemos) fazer é criar um único decodificador personalizado "tipo-neutro" que lida apenas com
null s, e se o valor BSON não for null , deve chamar o decodificador padrão para lidar com o não-null valor. Isso é surpreendentemente simples:
type nullawareDecoder struct {
defDecoder bsoncodec.ValueDecoder
zeroValue reflect.Value
}
func (d *nullawareDecoder) DecodeValue(dctx bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if vr.Type() != bsontype.Null {
return d.defDecoder.DecodeValue(dctx, vr, val)
}
if !val.CanSet() {
return errors.New("value not settable")
}
if err := vr.ReadNull(); err != nil {
return err
}
// Set the zero value of val's type:
val.Set(d.zeroValue)
return nil
}
Nós apenas temos que descobrir o que usar para
nullawareDecoder.defDecoder . Para isso, podemos usar o registro padrão:bson.DefaultRegistry , podemos pesquisar o decodificador padrão para tipos individuais. Frio. Então o que fazemos agora é registrar um valor do nosso
nullawareDecoder para todos os tipos que queremos tratar null s para. Não é tão difícil. Nós apenas listamos os tipos (ou valores desses tipos) para os quais queremos isso, e podemos cuidar de tudo com um simples loop:customValues := []interface{}{
"", // string
int(0), // int
int32(0), // int32
}
rb := bson.NewRegistryBuilder()
for _, v := range customValues {
t := reflect.TypeOf(v)
defDecoder, err := bson.DefaultRegistry.LookupDecoder(t)
if err != nil {
panic(err)
}
rb.RegisterDecoder(t, &nullawareDecoder{defDecoder, reflect.Zero(t)})
}
clientOpts := options.Client().
ApplyURI("mongodb://localhost:27017/").
SetRegistry(rb.Build())
client, err := mongo.Connect(ctx, clientOpts)
No exemplo acima, registrei decodificadores com reconhecimento de nulo para
string , int e int32 , mas você pode estender esta lista ao seu gosto, basta adicionar valores dos tipos desejados ao customValues fatia acima.