MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Wie ignoriert man Nullen beim Unmarshalling eines MongoDB-Dokuments?

Das Problem ist, dass die aktuellen bson-Codecs die Codierung/Decodierung von string nicht unterstützen in / von null .

Eine Möglichkeit, dies zu handhaben, besteht darin, einen benutzerdefinierten Decoder für string zu erstellen Typ, in dem wir mit null umgehen Werte:Wir verwenden nur die leere Zeichenkette (und, was noch wichtiger ist, melden keine Fehler).

Benutzerdefinierte Decoder werden durch den Typ bsoncodec.ValueDecoder beschrieben . Sie können bei einer bsoncodec.Registry registriert werden , mit einem bsoncodec.RegistryBuilder zum Beispiel.

Registrierungen können auf mehreren Ebenen festgelegt / angewendet werden, sogar auf einen ganzen mongo.Client , oder zu einer mongo.Database oder einfach zu einer mongo.Collection , beim Erwerb im Rahmen ihrer Möglichkeiten, z.B. options.ClientOptions.SetRegistry() .

Lassen Sie uns zunächst sehen, wie wir dies für string tun können , und als nächstes werden wir sehen, wie wir die Lösung für jeden Typ verbessern / verallgemeinern können.

1. Umgang mit null Saiten

Das Wichtigste zuerst, lassen Sie uns einen benutzerdefinierten String-Decoder erstellen, der einen null umwandeln kann in einen (n leeren) String:

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, und jetzt sehen wir uns an, wie dieser benutzerdefinierte String-Decoder für einen mongo.Client verwendet wird :

clientOpts := options.Client().
    ApplyURI("mongodb://localhost:27017/").
    SetRegistry(
        bson.NewRegistryBuilder().
            RegisterDecoder(reflect.TypeOf(""), nullawareStrDecoder{}).
            Build(),
    )
client, err := mongo.Connect(ctx, clientOpts)

Ab sofort mit diesem client , wenn Sie Ergebnisse in string dekodieren Werte, dieser registrierte nullawareStrDecoder decoder wird aufgerufen, um die Konvertierung durchzuführen, die bson null akzeptiert Werte und legt den leeren String "" fest .

Aber wir können es besser machen... Lesen Sie weiter...

2. Umgang mit null Werte beliebigen Typs:„typneutraler“ nullbewusster Decoder

Eine Möglichkeit wäre, einen separaten, benutzerdefinierten Decoder zu erstellen und ihn für jeden Typ zu registrieren, den wir handhaben möchten. Das scheint eine Menge Arbeit zu sein.

Was wir stattdessen tun können (und sollten), ist, einen einzigen, "typneutralen" benutzerdefinierten Decoder zu erstellen, der nur null behandelt s, und wenn der BSON-Wert nicht null ist , sollte den Standarddecoder aufrufen, um den Nicht-null zu verarbeiten Wert.

Das ist überraschend einfach:

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
}

Wir müssen nur herausfinden, was wir für nullawareDecoder.defDecoder verwenden . Dazu können wir die Standardregistrierung verwenden:bson.DefaultRegistry , können wir den Standarddecoder für einzelne Typen nachschlagen. Cool.

Also registrieren wir jetzt einen Wert unseres nullawareDecoder für alle Typen wollen wir mit null umgehen s für. Es ist nicht so schwer. Wir listen einfach die Typen (oder Werte dieser Typen) auf, für die wir das wollen, und wir können uns mit einer einfachen Schleife um alles kümmern:

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)

Im obigen Beispiel habe ich nullfähige Decoder für string registriert , int und int32 , aber Sie können diese Liste nach Belieben erweitern, indem Sie einfach Werte der gewünschten Typen zu den customValues hinzufügen Scheibe oben.