Как маршалировать / демаршалировать массив bson с полиморфной структурой с помощью mongo-go-driver

Я изо всех сил пытаюсь маршалировать / демаршалировать массив bson с полиморфной структурой с помощью mongo-go-driver。 Я планирую сохранить структурный дискриминатор в маршалированные данные и написать пользовательскую функцию UnmarshalBSONValue, чтобы декодировать ее в соответствии с структурным дискриминатором. Но я не знаю, как это правильно делать.

package polymorphism

import (
    "fmt"
    "testing"

    "code.byted.org/gopkg/pkg/testing/assert"

    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/bsontype"
)

type INode interface {
    GetName() string
}

type TypedNode struct {
    NodeClass string
}

type Node struct {
    TypedNode `bson:"inline"`
    Name      string
    Children  INodeList
}

func (n *Node) GetName() string {
    return n.Name
}

type INodeList []INode

func (l *INodeList) UnmarshalBSONValue(t bsontype.Type, data []byte) error {
    fmt.Println("INodeList.UnmarshalBSONValue")

    var arr []bson.Raw                // 1. First, try to decode data as []bson.Raw
    err := bson.Unmarshal(data, &arr) // error: cannot decode document into []bson.Raw
    if err != nil {
        fmt.Println(err)
        return err
    }
    for _, item := range arr { // 2. Then, try to decode each bson.Raw as concrete Node according to `nodeclass`
        class := item.Lookup("nodeclass").StringValue()
        fmt.Printf("class: %v\n", class)
        if class == "SubNode1" {
            bin, err := bson.Marshal(item)
            if err != nil {
                return err
            }
            var sub1 SubNode1
            err = bson.Unmarshal(bin, &sub1)
            if err != nil {
                return err
            }
            *l = append(*l, &sub1)
        } else if class == "SubNode2" {
            //...
        }
    }
    return nil
}

type SubNode1 struct {
    *Node     `bson:"inline"`
    FirstName string
    LastName  string
}

type SubNode2 struct {
    *Node `bson:"inline"`
    Extra string
}

С помощью приведенного выше кода я пытаюсь декодировать данные INodeList как []bson.Raw, а затем декодировать каждый bson.Raw как конкретный узел в соответствии с nodeclass. Но он сообщает об ошибке:

cannot decode document into []bson.Raw

на линии

err := bson.Unmarshal(data, &arr).

Итак, как это сделать правильно?


person ricky    schedule 05.01.2020    source источник


Ответы (1)


Вам нужно передать указатель bson.Raw на bson.Unmarshal(data, &arr), а затем нарезать его значение на массив необработанных значений, например:

func (l *INodeList) UnmarshalBSONValue(t bsontype.Type, data []byte) error {
    fmt.Println("INodeList.UnmarshalBSONValue")

    var raw bson.Raw // 1. First, try to decode data as bson.Raw
    err := bson.Unmarshal(data, &raw)
    if err != nil {
        fmt.Println(err)
        return err
    }

    // Slice the raw document to an array of valid raw values
    rawNodes, err := raw.Values()
    if err != nil {
        return err
    }

    // 2. Then, try to decode each bson.Raw as concrete Node according to `nodeclass`
    for _, rawNode := range rawNodes {
        // Convert the raw node to a raw document in order to access its "nodeclass" field
        d, ok := rawNode.DocumentOK()
        if !ok {
            return fmt.Errorf("raw node can't be converted to doc")
        }
        class := d.Lookup("nodeclass").StringValue()

        // Decode the node's raw doc to the corresponding struct
        var node INode
        switch class {
        case "SubNode1":
            node = &SubNode1{}
        case "SubNode2":
            node = &SubNode2{}
        //...
        default:
            // ...
        }

        bson.Unmarshal(d, node)
        *l = append(*l, node)
    }

    return nil
}

Обратите внимание, что Note.Children должен быть указателем INodeList, а встроенные поля должны быть структурой или картой (не указателем):


type Node struct {
    TypedNode `bson:",inline"`
    Name      string
    Children  *INodeList
}

type SubNode1 struct {
    Node      `bson:",inline"`
    FirstName string
    LastName  string
}

type SubNode2 struct {
    Node  `bson:",inline"`
    Extra string
}


person reda la    schedule 05.01.2020
comment
Работайте как шарм! Спасибо. Чтобы иметь дело с nil Children, следует добавить строку в UnmarshalBSONValue: if len(data) == 0 { return nil } - person ricky; 06.01.2020