MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Como ignorar nulos ao desempacotar um documento do MongoDB?


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.