El problema es que los códecs bson actuales no admiten la codificación/descodificación de string
en / desde null
.
Una forma de manejar esto es crear un decodificador personalizado para string
tipo en el que manejamos null
valores:solo usamos la cadena vacía (y, lo que es más importante, no informamos de errores).
Los decodificadores personalizados se describen con el tipo bsoncodec.ValueDecoder
. Se pueden registrar en un bsoncodec.Registry
, usando un bsoncodec.RegistryBuilder
por ejemplo.
Los registros se pueden configurar/aplicar en múltiples niveles, incluso a un mongo.Client
completo , o a una mongo.Database
o simplemente a una mongo.Collection
, al adquirirlos, como parte de sus opciones, p. options.ClientOptions.SetRegistry()
.
Primero, veamos cómo podemos hacer esto para string
, ya continuación veremos cómo mejorar/generalizar la solución a cualquier tipo.
1. Manejo de null
cuerdas
Lo primero es lo primero, creemos un decodificador de cadena personalizado que pueda convertir un null
en una cadena (n vacía):
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
}
Bien, ahora veamos cómo utilizar este decodificador de cadenas personalizado para un mongo.Client
:
clientOpts := options.Client().
ApplyURI("mongodb://localhost:27017/").
SetRegistry(
bson.NewRegistryBuilder().
RegisterDecoder(reflect.TypeOf(""), nullawareStrDecoder{}).
Build(),
)
client, err := mongo.Connect(ctx, clientOpts)
De ahora en adelante, usando este client
, cada vez que decodifique los resultados en string
valores, este registrado nullawareStrDecoder
se llamará al decodificador para manejar la conversión, que acepta bson null
valores y establece la cadena vacía Go ""
.
Pero podemos hacerlo mejor... Sigue leyendo...
2. Manejo de null
valores de cualquier tipo:decodificador de reconocimiento nulo "tipo neutral"
Una forma sería crear un decodificador personalizado separado y registrarlo para cada tipo que deseemos manejar. Eso parece ser mucho trabajo.
Lo que podemos (y debemos) hacer en su lugar es crear un único decodificador personalizado "de tipo neutral" que maneje solo null
s, y si el valor BSON no es null
, debe llamar al decodificador predeterminado para manejar el no null
valor.
Esto es sorprendentemente simple:
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
}
Solo tenemos que averiguar qué usar para nullawareDecoder.defDecoder
. Para esto podemos usar el registro por defecto:bson.DefaultRegistry
, podemos buscar el decodificador predeterminado para tipos individuales. Genial.
Entonces, lo que hacemos ahora es registrar un valor de nuestro nullawareDecoder
para todos los tipos que queremos manejar null
s para. No es tan dificil. Simplemente enumeramos los tipos (o valores de esos tipos) para los que queremos esto, y podemos encargarnos de todo con un simple bucle:
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)
En el ejemplo anterior, registré decodificadores que reconocen nulos para string
, int
y int32
, pero puede extender esta lista a su gusto, solo agregue valores de los tipos deseados a customValues
rebanada arriba.