Il problema è che gli attuali codec bson non supportano la codifica/decodifica di string
in/da null
.
Un modo per gestirlo è creare un decodificatore personalizzato per string
digita in cui gestiamo null
valori:utilizziamo solo la stringa vuota (e soprattutto non segnaliamo errori).
I decoder personalizzati sono descritti dal tipo bsoncodec.ValueDecoder
. Possono essere registrati in un bsoncodec.Registry
, utilizzando un bsoncodec.RegistryBuilder
per esempio.
I registri possono essere impostati/applicati a più livelli, anche a un intero mongo.Client
o a un mongo.Database
o semplicemente a una mongo.Collection
, al momento dell'acquisizione, come parte delle loro opzioni, ad es. options.ClientOptions.SetRegistry()
.
Per prima cosa vediamo come possiamo farlo per string
, e successivamente vedremo come migliorare/generalizzare la soluzione a qualsiasi tipo.
1. Gestione null
stringhe
Per prima cosa, creiamo un decodificatore di stringhe personalizzato che può girare a null
in una stringa (n vuota):
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 ora vediamo come utilizzare questo decodificatore di stringhe personalizzato su 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'ora in poi, utilizzando questo client
, ogni volta che decodifichi i risultati in string
valori, questo ha registrato nullawareStrDecoder
il decoder verrà chiamato per gestire la conversione, che accetta bson null
valori e imposta la stringa vuota Go ""
.
Ma possiamo fare di meglio... Continua a leggere...
2. Gestione null
valori di qualsiasi tipo:decodificatore null-aware "type-neutral"
Un modo sarebbe creare un decoder personalizzato separato e registrarlo per ogni tipo che desideriamo gestire. Sembra che ci sia molto lavoro.
Quello che potremmo (e dovremmo) fare invece è creare un singolo decodificatore personalizzato "indipendente dal tipo" che gestisca solo null
se il valore BSON non è null
, dovrebbe chiamare il decodificatore predefinito per gestire il non-null
valore.
Questo è sorprendentemente semplice:
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
}
Dobbiamo solo capire cosa usare per nullawareDecoder.defDecoder
. Per questo possiamo usare il registro di default:bson.DefaultRegistry
, potremmo cercare il decodificatore predefinito per i singoli tipi. Fantastico.
Quindi quello che facciamo ora è registrare un valore del nostro nullawareDecoder
per tutti i tipi vogliamo gestire null
s per. Non è così difficile. Elenchiamo solo i tipi (o i valori di quei tipi) per cui vogliamo questo e possiamo occuparci di tutto con un semplice ciclo:
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)
Nell'esempio sopra ho registrato decoder null-aware per string
, int
e int32
, ma puoi estendere questo elenco a tuo piacimento, basta aggiungere i valori dei tipi desiderati a customValues
fetta sopra.