Использование драйвера mongodb go для декодирования документов в структуры с полями нестандартного типа

Я новичок в обоих го и mongodb. Я пытаюсь расшифровать DocumentResult в структуру с использованием тегов bson, и она не работает для пользовательского типа, заключающего строку. Можно ли это сделать, не меняя тип поля на строку?

    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)

ПЕЧАТНЫЙ РЕЗУЛЬТАТ: "42 The Answer" //"ABCD" отсутствует

заранее спасибо

2 ответа

Решение

К сожалению, вам не повезло. Текущее состояние официального драйвера Монго Го не поддерживает демаршалинг string значения от BSON до значения Go, тип которого является пользовательским типом, имеющим string как его основной тип. Это может измениться в будущем, но пока это не поддерживается.

Способ декодирования в поле структуры реализован в bson/decode.go В настоящее время строка № 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 тип строки BSON Попытка декодирования в поле структуры выполняется только в том случае, если тип поля структуры является одним из следующих: string, interface{}, json.Number или же url.URL (или указатель на них).

К сожалению реализация bson.Unmarshaler Ваш пользовательский тип также не помогает, так как он не проверяется в случае полей структуры, только если сама структура реализует его. Но, реализуя саму структуру, вам придется дублировать структуру с полем, являющимся одним из перечисленных выше поддерживаемых типов (или использовать карту или bson.Document тип).

Это серьезное ограничение со стороны библиотеки, которое очень легко решить, поэтому давайте надеяться на лучшее, что они добавят поддержку для этого в ближайшем будущем.

Я пытаюсь декодировать DocumentResult в структуру с помощью тегов bson, и он не работает для пользовательского типа, заключающего строку

С вашим текущим MyTypeдокумент, который будет храниться в MongoDB, будет выглядеть следующим образом:

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

Хотя базовый тип является stringэто может быть сложно декодировать с текущей версией mongo-go-driver (v0.0.12) из-за переноса типа.

Однако, если вы хотите иметь собственный тип как таковой, вы можете вместо этого изменить структуру на встроенное поле. Например:

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

Документ будет храниться в MongoDB, как показано ниже:

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

В противном случае просто используйте string введите напрямую, потому что результирующий документ в базе данных будет таким же, как версия переноса типа:

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

Вам также может пригодиться MongoDB Data Modeling.

С версиями 1.x драйвера MongoDB для Go (последняя версия на момент написания - 1.3.1) полностью возможно кодировать и декодировать типы с псевдонимами.

Ваш пример теперь работает, как ожидалось, при условии, что вы настроили mongo.Connect соответствовать новому 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)
}

Это возвращает: 42 The Answer ABCD как и ожидалось

Другие вопросы по тегам