Search code examples
gomongo-go

BSON map to database models struct


Struggling to figure out the correct way to do this. Right now I can convert a bson map to my database model structs individually. But now I'm just repeating a lot of the same code. So is there a better way to do this?

Example of code:

type Agent struct {
    FirstName      string   `bson:"firstName,omitempty" json:"firstName,omitempty"`
    LastName       string   `bson:"lastName,omitempty" json:"lastName,omitempty"`
    ProfileImage   string   `bson:"profileImage,omitempty" json:"profileImage,omitempty"`
}

func BSONToAgent(bsonMap primitive.M) (agent Agent, err error) {
    bsonBytes, err := bson.Marshal(bsonMap)
    if err != nil {
        return agent, err
    }
    bson.Unmarshal(bsonBytes, &agent)
    return agent, nil
}

func BSONArrayToAgents(bsonMap []primitive.M) (agents []Agent, err error) {
    for _, item := range bsonMap {
        agent, err := BSONToAgent(item)
        if err != nil {
            return agents, err
        }
        agents = append(agents, agent)
    }
    return agents, nil
}



type Form struct {
    UserID        primitive.ObjectID `bson:"userId,omitempty" json:"userId,omitempty"`
    Name          string             `bson:"name,omitempty" json:"name,omitempty"`
    CreatedAt     time.Time          `bson:"createdAt,omitempty" json:"createdAt,omitempty"`
    UpdatedAt     time.Time          `bson:"updatedAt,omitempty" json:"updatedAt,omitempty"`
}

func BSONArrayToForms(bsonMap []primitive.M) (forms []Form, err error) {
    for _, item := range bsonMap {
        form, err := BSONToForm(item)
        if err != nil {
            return forms, err
        }
        forms = append(forms, form)
    }
    return forms, nil
}

func BSONToForm(bsonMap primitive.M) (form Form, err error) {
    bsonBytes, err := bson.Marshal(bsonMap)
    if err != nil {
        return form, err
    }
    bson.Unmarshal(bsonBytes, &form)
    return form, nil
}



If you look at functions BSONToAgent and BSONToForm they are pretty much the same function just with a different Type that it returns. And the same goes for BSONArrayToAgents and BSONArrayToForms. Now I want to implement these functions on all my database models to make it easy to convert a primitive.M (bson map) that is returned when querying the database.

Is there a better way to do this? Maybe using an interface?


Solution

  • If you're using marshal/unmarshal as a means to translating bson, you can do:

    func BSONToForm(bsonMap primitive.M, out interface{}) (err error) {
        bsonBytes, err := bson.Marshal(bsonMap)
        if err != nil {
            return form, err
        }
        return bson.Unmarshal(bsonBytes, out)
    }
    
    var v Form
    BSONToForm(bsonMap,&v)
    

    This doesn't work as nicely for arrays though, so you can do something like this:

    func BSONArrayToForms(bsonMap []primitive.M, newItem func() interface{}, add func(interface{})) (err error) {
        for _, item := range bsonMap {
            n:=newItem()
            err := BSONToForm(item,n)
            if err != nil {
                return forms, err
            }
            add(n)
        }
        return nil
    }
    
    forms:=make([]Form,0)
    BSONArrayToForms(bsonArr,func() interface{} {return &Form{}},func(in interface{}) {
       forms=append(forms,*(in.(*Form)))
    })
    

    Or a single-func variant:

    func BSONArrayToForms(bsonMap []primitive.M, newItem func() interface{}) (err error) {
        for _, item := range bsonMap {
            n:=newItem()
            err := BSONToForm(item,n)
            if err != nil {
                return forms, err
            }
        }
        return nil
    }
    
    forms:=make([]Form,0)
    BSONArrayToForms(bsonArr,func() interface{} {
       forms=append(forms,Form{})
       return &forms[len(forms)-1]})
    
    

    This version has to make sure that the returned pointer is also stored in the array.