Проблемът е, че текущите bson кодеци не поддържат кодиране / декодиране на string
в / от null
.
Един от начините да се справите с това е да създадете персонализиран декодер за string
тип, в който обработваме null
стойности:ние просто използваме празния низ (и по-важното не съобщаваме за грешка).
Персонализираните декодери се описват от типа bsoncodec.ValueDecoder
. Те могат да бъдат регистрирани в bsoncodec.Registry
, като използвате bsoncodec.RegistryBuilder
например.
Регистрите могат да се задават/прилагат на множество нива, дори към цял mongo.Client
, или към mongo.Database
или просто към mongo.Collection
, при придобиването им, като част от техните опции, напр. options.ClientOptions.SetRegistry()
.
Първо нека видим как можем да направим това за string
, а след това ще видим как да подобрим/обобщим решението за всеки тип.
1. Обработка на null
струни
Първо, нека създадем персонализиран декодер на низове, който може да превърне null
в (n празен) низ:
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
}
Добре, а сега нека да видим как да използваме този персонализиран низ декодер за mongo.Client
:
clientOpts := options.Client().
ApplyURI("mongodb://localhost:27017/").
SetRegistry(
bson.NewRegistryBuilder().
RegisterDecoder(reflect.TypeOf(""), nullawareStrDecoder{}).
Build(),
)
client, err := mongo.Connect(ctx, clientOpts)
Отсега нататък, използвайки този client
, когато декодирате резултатите в string
стойности, този регистриран nullawareStrDecoder
декодерът ще бъде извикан за обработка на преобразуването, което приема bson null
стойности и задава празния низ ""
.
Но можем да направим по-добре... Прочетете...
2. Обработка на null
стойности от всякакъв тип:"type-neutral" декодер с нулева информация
Един от начините би бил да създадем отделен персонализиран декодер и да го регистрираме за всеки тип, с който искаме да работим. Това изглежда е много работа.
Това, което можем (и трябва) да направим вместо това, е да създадем един-единствен, "неутрален по тип" персонализиран декодер, който обработва само null
s и ако стойността на BSON не е null
, трябва да извика декодера по подразбиране, за да обработва не-null
стойност.
Това е изненадващо просто:
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
}
Просто трябва да разберем какво да използваме за nullawareDecoder.defDecoder
. За това можем да използваме системния регистър по подразбиране:bson.DefaultRegistry
, можем да търсим декодера по подразбиране за отделни типове. Готино.
Така че това, което правим сега, е да регистрираме стойност на нашия nullawareDecoder
за всички типове искаме да обработваме null
s за. Не е толкова трудно. Ние просто изброяваме типовете (или стойностите на тези типове), за които искаме това, и можем да се погрижим за всичко с обикновен цикъл:
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)
В примера по-горе регистрирах нулеви декодери за string
, int
и int32
, но можете да разширите този списък по ваш вкус, просто добавете стойности на желаните типове към customValues
парче по-горе.