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.