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.