Problemet er, at de nuværende bson-codecs ikke understøtter kodning / afkodning af string
ind i / fra null
.
En måde at håndtere dette på er at oprette en brugerdefineret dekoder til string
type, hvor vi håndterer null
værdier:vi bruger bare den tomme streng (og endnu vigtigere rapporterer ikke fejl).
Brugerdefinerede dekodere er beskrevet af typen bsoncodec.ValueDecoder
. De kan registreres i et bsoncodec.Registry
, ved hjælp af en bsoncodec.RegistryBuilder
for eksempel.
Registre kan indstilles / anvendes på flere niveauer, selv til en hel mongo.Client
, eller til en mongo.Database
eller bare til en mongo.Collection
, når de erhverver dem, som en del af deres muligheder, f.eks. options.ClientOptions.SetRegistry()
.
Lad os først se, hvordan vi kan gøre dette for string
, og derefter skal vi se, hvordan man forbedrer/genererer løsningen til enhver type.
1. Håndtering af null
strenge
Først og fremmest, lad os skabe en brugerdefineret strengdekoder, der kan ændre en null
ind i en (n tom) streng:
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, og lad os nu se, hvordan man bruger denne tilpassede strengdekoder til en mongo.Client
:
clientOpts := options.Client().
ApplyURI("mongodb://localhost:27017/").
SetRegistry(
bson.NewRegistryBuilder().
RegisterDecoder(reflect.TypeOf(""), nullawareStrDecoder{}).
Build(),
)
client, err := mongo.Connect(ctx, clientOpts)
Fra nu af skal du bruge denne client
, hver gang du afkoder resultater til string
værdier, denne registrerede nullawareStrDecoder
dekoder vil blive kaldt til at håndtere konverteringen, som accepterer bson null
værdier og indstiller den tomme streng ""
.
Men vi kan gøre det bedre... Læs videre...
2. Håndtering af null
værdier af enhver type:"type-neutral" null-bevidst dekoder
En måde ville være at oprette en separat, brugerdefineret dekoder og registrere den for hver type, vi ønsker at håndtere. Det ser ud til at være meget arbejde.
Hvad vi kan (og bør) gøre i stedet er at skabe en enkelt "type-neutral" brugerdefineret dekoder, som kun håndterer null
s, og hvis BSON-værdien ikke er null
, skal kalde standarddekoderen til at håndtere ikke-null
værdi.
Dette er overraskende enkelt:
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
}
Vi skal bare finde ud af, hvad vi skal bruge til nullawareDecoder.defDecoder
. Til dette kan vi bruge standardregistret:bson.DefaultRegistry
, kan vi slå standarddekoderen op for individuelle typer. Fedt.
Så det, vi gør nu, er at registrere en værdi af vores nullawareDecoder
for alle typer, vi ønsker at håndtere null
s for. Det er ikke så svært. Vi lister bare de typer (eller værdier af disse typer), vi ønsker dette for, og vi kan tage os af det hele med en simpel løkke:
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)
I eksemplet ovenfor registrerede jeg null-bevidste dekodere for string
, int
og int32
, men du kan udvide denne liste til din smag, bare tilføje værdier af de ønskede typer til customValues
udsnit ovenfor.