Search code examples
mongodbgostructslicemongo-go

Passing in dynamic struct into function in golang


I having a couple of structs, Products and Categories. I have 2 functions listed below that have identical logic just different structs that are being used and returned. Is there anyway I can abstract out the struct data types and use the same logic in a single function called GetObjects?

func GetCategories(collection *mongo.Collection) []Category {
    ctx := context.Background()
    cats := []Category{}
    cur, err := collection.Find(ctx, bson.M{})
    if err != nil {
        log.Fatal("Error: ", err)
    }
    for cur.Next(context.TODO()) {
        var cat Category
        err = cur.Decode(&cat)
        if err != nil {
            log.Fatal(err)
        }
        cats = append(cats, cat)
    }
    return cats
}

func GetProducts(collection *mongo.Collection) []Product {
    ctx := context.Background()
    prods := []Product{}
    cur, err := collection.Find(ctx, bson.M{})
    if err != nil {
        log.Fatal("Error: ", err)
    }
    for cur.Next(context.TODO()) {
        var prod Product
        err = cur.Decode(&prod)
        if err != nil {
            log.Fatal(err)
        }
        prods = append(prods, prod)
    }
    return prods
}

Solution

  • You could create a generalized GetObjs() if you would pass in the destination where you want the results to be loaded.

    Something like:

    func GetObjs(c *mongo.Collection, dst interface{})
    

    And callers are responsible to pass a ready slice or a pointer to a slice variable where results will be stored.

    Also note that context.Context should be passed and not created arbitrarily inside the function:

    func GetObjs(ctx context.Context, c *mongo.Collection, dst interface{})
    

    Also, errors should be returned and not "swallowed", so if one occurs, it can be dealt with appropriately at the caller:

    func GetObjs(ctx context.Context, c *mongo.Collection, dst interface{}) error
    

    Also, if you need all results, you don't need to iterate over them and decode all one-by-one. Just use Cursor.All().

    This is how the "improved" GetObjs() could look like:

    func GetObjs(ctx context.Context, c *mongo.Collection, dst interface{}) error {
        cur, err := c.Find(ctx, bson.M{})
        if err != nil {
            return err
        }
        return cur.All(ctx, dst)
    }
    

    (Although this became quite simple, not sure it warrants its own existence.)

    And this is how you could use it:

    ctx := ... // Obtain context, e.g. from the request: r.Context()
    c := ... // Collection you want to query from
    
    var cats []Category
    if err := GetObjs(ctx, c, &cats); err != nil {
        // Handle error
        return
    }
    // Use cats
    
    var prods []Product
    if err := GetObjs(ctx, c, &prods); err != nil {
        // Handle error
        return
    }
    // Use prods