MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Comment ignorer les valeurs nulles lors du démarshalling d'un document MongoDB ?

Le problème est que les codecs bson actuels ne supportent pas l'encodage/décodage string dans / depuis null .

Une façon de gérer cela est de créer un décodeur personnalisé pour string type dans lequel on gère null valeurs :nous utilisons simplement la chaîne vide (et, plus important encore, nous ne signalons pas d'erreur).

Les décodeurs personnalisés sont décrits par le type bsoncodec.ValueDecoder . Ils peuvent être enregistrés dans un bsoncodec.Registry , en utilisant un bsoncodec.RegistryBuilder par exemple.

Les registres peuvent être définis/appliqués à plusieurs niveaux, même à tout un mongo.Client , ou à une mongo.Database ou juste à une mongo.Collection , lors de leur acquisition, dans le cadre de leurs options, par ex. options.ClientOptions.SetRegistry() .

Voyons d'abord comment nous pouvons faire cela pour string , et ensuite nous verrons comment améliorer / généraliser la solution à tout type.

1. Gestion de null chaînes

Tout d'abord, créons un décodeur de chaîne personnalisé qui peut transformer un null dans une chaîne (n vide) :

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, et voyons maintenant comment utiliser ce décodeur de chaîne personnalisé pour un mongo.Client :

clientOpts := options.Client().
    ApplyURI("mongodb://localhost:27017/").
    SetRegistry(
        bson.NewRegistryBuilder().
            RegisterDecoder(reflect.TypeOf(""), nullawareStrDecoder{}).
            Build(),
    )
client, err := mongo.Connect(ctx, clientOpts)

Désormais, en utilisant ce client , chaque fois que vous décodez les résultats en string valeurs, ce nullawareStrDecoder enregistré le décodeur sera appelé pour gérer la conversion, qui accepte bson null valeurs et définit la chaîne vide Go "" .

Mais nous pouvons faire mieux... Lisez la suite...

2. Gestion de null valeurs de n'importe quel type :décodeur "neutral" prenant en compte les valeurs nulles

Une façon serait de créer un décodeur personnalisé séparé et de l'enregistrer pour chaque type que nous souhaitons gérer. Cela semble être beaucoup de travail.

Ce que nous pouvons (et devrions) faire à la place est de créer un seul décodeur personnalisé "de type neutre" qui gère uniquement null s, et si la valeur BSON n'est pas null , devrait appeler le décodeur par défaut pour gérer le non-null valeur.

C'est étonnamment 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
}

Nous devons juste déterminer ce qu'il faut utiliser pour nullawareDecoder.defDecoder . Pour cela, nous pouvons utiliser le registre par défaut :bson.DefaultRegistry , nous pouvons rechercher le décodeur par défaut pour des types individuels. Cool.

Donc, ce que nous faisons maintenant, c'est enregistrer une valeur de notre nullawareDecoder pour tous les types, nous voulons gérer null s pour. Ce n'est pas si dur. Nous listons simplement les types (ou les valeurs de ces types) pour lesquels nous voulons cela, et nous pouvons nous occuper de tout avec une simple boucle :

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)

Dans l'exemple ci-dessus, j'ai enregistré des décodeurs null-aware pour string , int et int32 , mais vous pouvez étendre cette liste à votre guise, ajoutez simplement des valeurs des types souhaités aux customValues tranche ci-dessus.