MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Come ignorare i valori null durante l'annullamento del marshalling di un documento MongoDB?

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.