Problém je v tom, že současné kodeky bson nepodporují kódování / dekódování string
do / z null
.
Jedním ze způsobů, jak to zvládnout, je vytvořit vlastní dekodér pro string
typ, ve kterém zpracováváme null
hodnoty:použijeme pouze prázdný řetězec (a co je důležitější, nehlásíme chybu).
Vlastní dekodéry jsou popsány typem bsoncodec.ValueDecoder
. Lze je zaregistrovat na bsoncodec.Registry
pomocí bsoncodec.RegistryBuilder
například.
Registry lze nastavit / použít na více úrovních, dokonce i na celý mongo.Client
nebo do mongo.Database
nebo jen do mongo.Collection
, při jejich pořízení v rámci svých opcí, např. options.ClientOptions.SetRegistry()
.
Nejprve se podívejme, jak to můžeme udělat pro string
a dále uvidíme, jak zlepšit / zobecnit řešení na jakýkoli typ.
1. Zpracování null
řetězce
Nejprve si vytvořte vlastní dekodér řetězců, který dokáže změnit null
do (n prázdného) řetězce:
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
}
Dobře, a nyní se podívejme, jak využít tento vlastní dekodér řetězců pro mongo.Client
:
clientOpts := options.Client().
ApplyURI("mongodb://localhost:27017/").
SetRegistry(
bson.NewRegistryBuilder().
RegisterDecoder(reflect.TypeOf(""), nullawareStrDecoder{}).
Build(),
)
client, err := mongo.Connect(ctx, clientOpts)
Od této chvíle pomocí tohoto client
, kdykoli dekódujete výsledky do string
hodnoty, tento registroval nullawareStrDecoder
bude zavolán dekodér, který provede konverzi, která akceptuje bson null
hodnoty a nastaví Go prázdný řetězec ""
.
Ale můžeme to udělat lépe... Čtěte dále...
2. Zpracování null
hodnoty libovolného typu:"typově neutrální" dekodér s nulovou znalostí
Jedním ze způsobů by bylo vytvořit samostatný vlastní dekodér a zaregistrovat jej pro každý typ, který chceme zpracovávat. Zdá se, že je to hodně práce.
Místo toho můžeme (a měli bychom) vytvořit jediný „typově neutrální“ vlastní dekodér, který zpracovává pouze null
s, a pokud hodnota BSON není null
, by měl zavolat výchozí dekodér, aby zpracoval nenull
hodnotu.
Je to překvapivě jednoduché:
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
}
Musíme jen vymyslet, co použít pro nullawareDecoder.defDecoder
. K tomu můžeme použít výchozí registr:bson.DefaultRegistry
, můžeme pro jednotlivé typy vyhledat výchozí dekodér. Skvělé.
Nyní tedy zaregistrujeme hodnotu našeho nullawareDecoder
pro všechny typy chceme zpracovat null
s pro. Není to tak těžké. Vypíšeme pouze typy (nebo hodnoty těchto typů), pro které to chceme, a o vše se můžeme postarat pomocí jednoduché smyčky:
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)
Ve výše uvedeném příkladu jsem zaregistroval dekodéry s nulovou hodnotou pro string
, int
a int32
, ale tento seznam můžete rozšířit podle svých představ, stačí přidat hodnoty požadovaných typů do customValues
plátek výše.