web-dev-qa-db-fra.com

Utilisation du pilote mongodb go pour décoder des documents en structures avec des champs de type personnalisés

Je suis débutant en go et en mongodb. J'essaie de décoder un DocumentResult en une structure à l'aide de balises bson, et cela ne fonctionne pas pour un type personnalisé enveloppant une chaîne. Peut-on le faire sans changer le type du champ en chaîne?

    import (
    "context"
    "github.com/mongodb/mongo-go-driver/mongo"
)

type MyDoc struct {
    SomeInt int `bson:"some_int"`
    SomeString string `bson:"some_string,omitempty"`
    CustomType MyType `bson:"custom_type,omitempty"`
}

type MyType string

const myType MyType = "ABCD"

func main() {

    //Connect to db
    client, _ := mongo.Connect(context.Background(), "mongodb://localhost:27017", nil)
    db := client.Database("example_db")
    collection := db.Collection("col")

    //Insert document
    docToInsert := MyDoc{42, "The Answer", myType}
    collection.InsertOne(nil, docToInsert)

    //Retrieve document
    filterDoc := MyDoc{SomeInt: 42}
    resultDoc := &MyDoc{}
    result := collection.FindOne(nil, filterDoc)
    result.Decode(resultDoc)

    println(resultDoc.SomeInt, resultDoc.SomeString, resultDoc.CustomType)

RÉSULTAT IMPRIMÉ: "42 La réponse" // "ABCD" est manquant

Merci d'avance

5
amz

Avant-propos: Les types personnalisés ayant string comme types sous-jacents sont désormais gérés automatiquement par le pilote. Cette réponse est antérieure au pilote 1.x versions où cela était nécessaire.


Malheureusement, vous n'avez pas de chance. L'état actuel du pilote officiel mongo go ne prend pas en charge le démasquage des valeurs string de BSON vers une valeur Go dont le type est un type personnalisé ayant string comme type sous-jacent. Cela peut changer à l'avenir, mais pour l'instant cela n'est pas pris en charge.

La façon dont le décodage dans un champ struct est géré est implémentée dans bson/decode.go, actuellement ligne # 387 :

case 0x2:
    str := v.StringValue()
    switch containerType {
    case tString, tEmpty:
        val = reflect.ValueOf(str)
    case tJSONNumber:
        _, err := strconv.ParseFloat(str, 64)
        if err != nil {
            return val, err
        }
        val = reflect.ValueOf(str).Convert(tJSONNumber)

    case tURL:
        u, err := url.Parse(str)
        if err != nil {
            return val, err
        }
        val = reflect.ValueOf(u).Elem()
    default:
        return val, nil
    }

0x02 est le type de chaîne BSON. Il est uniquement tenté de décoder dans le champ struct si le type du champ struct est l'un des suivants: string, interface{}, json.Number ou url.URL (ou un pointeur vers ceux-ci).

Malheureusement, la mise en œuvre bson.Unmarshaler sur votre type personnalisé n'aide pas non plus, car il n'est pas vérifié dans le cas des champs de struct, seulement si le struct lui-même l'implémente. Mais en implémentant sur la structure elle-même, vous devrez dupliquer la structure avec le champ étant l'un des types pris en charge répertoriés ci-dessus (ou utiliser une carte ou un bson.Document type).

Il s'agit d'une sérieuse limitation de la part de la bibliothèque qui peut être résolue très facilement, alors espérons pour le mieux qu'ils ajoutent un support pour cela dans un proche avenir.

1
icza

J'essaie de décoder un DocumentResult en une structure à l'aide de balises bson, et cela ne fonctionne pas pour un type personnalisé enveloppant une chaîne

Avec votre MyType actuel, le document qui serait stocké dans MongoDB serait comme ci-dessous:

{
  "_id": ObjectId("..."),
  "some_int": NumberLong("42"),
  "some_string": "The Answer",
  "custom_type": "ABCD"
}

Même si le type sous-jacent est un string, cela pourrait être difficile à décoder avec la version actuelle de mongo-go-driver (v0.0.12) en raison de l'habillage de type.

Cependant, si vous souhaitez avoir un type personnalisé en tant que tel, vous pouvez remplacer la structure par n champ incorporé à la place. Par exemple:

type MyDoc struct {
    SomeInt    int    `bson:"some_int"`
    SomeString string `bson:"some_string,omitempty"`
    CustomType MyType `bson:"custom_type,omitempty"`
}

type MyType struct {
    Value string `bson:"value,omitempty"`
}

var myType = MyType{Value: "ABCD"}

docToInsert := MyDoc{42, "The Answer", "ABCD"}

insertResult, err := collection.InsertOne(nil, docToInsert)

resultDoc := collection.FindOne(context.Background(), nil)
if err != nil {
    log.Fatal(err)
}
elem := &MyDoc{}
err = resultDoc.Decode(elem)
if err != nil {
    log.Fatal(err)
}
fmt.Println(elem.SomeInt, elem.SomeString, elem.CustomType.Value)
// 42 The Answer ABCD

Le document serait stocké dans MongoDB comme ci-dessous:

{
  "_id": ObjectId("..."),
  "some_int": NumberLong("42"),
  "some_string": "The Answer",
  "custom_type": {
    "value": "ABCD"
  }
}

Sinon, utilisez simplement le type string directement car le document résultant dans la base de données serait le même que la version d'habillage de type:

type MyDoc struct {
    SomeInt    int    `bson:"some_int"`
    SomeString string `bson:"some_string,omitempty"`
    CustomType string `bson:"custom_type,omitempty"`
} 

Vous pouvez également trouver MongoDB Data Modeling une référence utile.

1
Wan Bachtiar

Avec les versions 1.x du pilote MongoDB pour Go (la dernière version au moment de la rédaction est 1.3.1), il est tout à fait possible de coder et de décoder les types alias.

Votre exemple fonctionne maintenant comme prévu, étant donné que l'on ajuste le mongo.Connect pour correspondre au nouveau 1.x API.

package main

import (
    "context"

    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

type MyDoc struct {
    SomeInt    int    `bson:"some_int"`
    SomeString string `bson:"some_string,omitempty"`
    CustomType MyType `bson:"custom_type,omitempty"`
}

type MyType string

const myType MyType = "ABCD"

func main() {

    // Connect to db
    clientOpts := options.Client().
        ApplyURI("mongodb://localhost/example_db")
    client, _ := mongo.Connect(context.Background(), clientOpts)
    db := client.Database("example_db")
    collection := db.Collection("col")

    // Insert document
    docToInsert := MyDoc{42, "The Answer", myType}
    collection.InsertOne(nil, docToInsert)

    // Retrieve document
    filterDoc := MyDoc{SomeInt: 42}
    resultDoc := &MyDoc{}
    result := collection.FindOne(nil, filterDoc)
    result.Decode(resultDoc)

    println(resultDoc.SomeInt, resultDoc.SomeString, resultDoc.CustomType)
}

Cela renvoie: 42 The Answer ABCD comme prévu

0
AlexejK