Problem polega na tym, że obecne kodeki bson nie obsługują kodowania / dekodowania string
do / z null
.
Jednym ze sposobów poradzenia sobie z tym jest stworzenie własnego dekodera dla string
typ, w którym obsługujemy null
wartości:po prostu używamy pustego ciągu (i, co ważniejsze, nie zgłaszamy błędu).
Dekodery niestandardowe są opisane przez typ bsoncodec.ValueDecoder
. Można je zarejestrować w bsoncodec.Registry
, używając bsoncodec.RegistryBuilder
na przykład.
Rejestry można ustawiać / stosować na wielu poziomach, nawet do całego mongo.Client
lub do mongo.Database
lub po prostu do mongo.Collection
, przy ich nabywaniu, w ramach posiadanych opcji, m.in. options.ClientOptions.SetRegistry()
.
Najpierw zobaczmy, jak możemy to zrobić dla string
, a następnie zobaczymy, jak ulepszyć / uogólnić rozwiązanie dla dowolnego typu.
1. Obsługa null
struny
Po pierwsze, stwórzmy niestandardowy dekoder ciągów znaków, który może zmienić null
do (n pustego) ciągu:
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, a teraz zobaczmy, jak wykorzystać ten niestandardowy dekoder ciągów do mongo.Client
:
clientOpts := options.Client().
ApplyURI("mongodb://localhost:27017/").
SetRegistry(
bson.NewRegistryBuilder().
RegisterDecoder(reflect.TypeOf(""), nullawareStrDecoder{}).
Build(),
)
client, err := mongo.Connect(ctx, clientOpts)
Od teraz używaj tego client
, za każdym razem, gdy dekodujesz wyniki na string
wartości, ten zarejestrowany nullawareStrDecoder
dekoder zostanie wywołany do obsługi konwersji, która akceptuje bson null
wartości i ustawia pusty ciąg Go ""
.
Ale możemy zrobić lepiej... Czytaj dalej...
2. Obsługa null
wartości dowolnego typu:"type-neutral" dekoder typu null-aware
Jednym ze sposobów byłoby stworzenie oddzielnego, niestandardowego dekodera i zarejestrowanie go dla każdego typu, który chcemy obsłużyć. To wydaje się być dużo pracy.
Zamiast tego możemy (i powinniśmy) stworzyć pojedynczy, "neutralny pod względem typu" dekoder, który obsługuje tylko null
s, a jeśli wartość BSON nie jest null
, powinien wywołać domyślny dekoder do obsługi wartości innych niż null
wartość.
To zaskakująco proste:
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
}
Musimy tylko dowiedzieć się, czego użyć dla nullawareDecoder.defDecoder
. W tym celu możemy użyć domyślnego rejestru:bson.DefaultRegistry
, możemy wyszukać domyślny dekoder dla poszczególnych typów. Fajnie.
Więc teraz rejestrujemy wartość naszego nullawareDecoder
dla wszystkich typów, które chcemy obsłużyć null
s dla. To nie jest takie trudne. Po prostu wymieniamy typy (lub wartości tych typów), dla których chcemy to zrobić, i możemy zająć się nimi za pomocą prostej pętli:
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)
W powyższym przykładzie zarejestrowałem dekodery null-aware dla string
, int
i int32
, ale możesz rozszerzyć tę listę według własnych upodobań, po prostu dodaj wartości żądanych typów do customValues
kawałek powyżej.