sql >> Base de Datos >  >> NoSQL >> MongoDB

¿Cómo ignorar los valores nulos al desarmar un documento MongoDB?

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.