How to return dynamic type struct in Golang?
Asked Answered
D

1

31

I am using Golang Revel for some web project and I did like 12 projects in that so far. In all of them I have a lot of code redundancy because of return types. Look at this two functions:

func (c Helper) Brands() []*models.Brand{

    //do some select on rethinkdb and populate correct model
    var brands []*models.Brand
    rows.All(&brands)

    return brands

}

func (c Helper) BlogPosts() []*models.Post{

    //do some select on rethinkdb and populate correct model
    var posts []*models.Post
    rows.All(&posts)

    return posts

}

As you can see they they both returns same type of data (type struct). My idea was just to pass string var like this:

func (c Helper) ReturnModels(modelName string) []*interface{} {

    //do rethinkdb select with modelName and return []*interface{} for modelName
}

Like this I can have just one helper for returning data types instead of doing same thing over and over again for different models but same data type.

My questions are:

  1. Is this possible at all
  2. If yes can you point me to right docs
  3. If no, I will be more then happy to return your answer :)
Drysalter answered 26/2, 2016 at 16:51 Comment(0)
I
47

Yes it's possible however your function should return interface{} and not []*interface.

func (c Helper) ReturnModels(modelName string) interface{} {}

In this case you could use Type Switches and/or Type Assertions to cast the return value into it's original type.

Example

Note: I've never used Revel, but the following snippet should give you an a general idea:

Playground

package main

import "fmt"

type Post struct {
    Author  string
    Content string
}

type Brand struct {
    Name string
}

var database map[string]interface{}

func init() {
    database = make(map[string]interface{})

    brands := make([]Brand, 2)
    brands[0] = Brand{Name: "Gucci"}
    brands[1] = Brand{Name: "LV"}

    database["brands"] = brands

    posts := make([]Post, 1)
    posts[0] = Post{Author: "J.K.R", Content: "Whatever"}

    database["posts"] = posts
}

func main() {
    fmt.Println("List of Brands: ")
    if brands, ok := ReturnModels("brands").([]Brand); ok {
        fmt.Printf("%v", brands)
    }

    fmt.Println("\nList of Posts: ")
    if posts, ok := ReturnModels("posts").([]Post); ok {
        fmt.Printf("%v", posts)
    }

}

func ReturnModels(modelName string) interface{} {

    return database[modelName]
}
Ixion answered 26/2, 2016 at 17:4 Comment(5)
Can you extend your answer a bit more on how to do it?Translator
You are AWESOME! I will shine your shoes any time :). I hate when I have redundancy when do not know how to do something. What you wrote just removed a lot of code from my project(s).Drysalter
Note that ReturnModels("brands").([]Brand) will panic if the type assertion fails. In this case it would as you should be asserting for []*models.Brand. Use the comma, ok idiom to protect against panics: golang.org/doc/effective_go.html#interface_conversionsHadlock
Excellent Point! I've updated my example to include the comma, ok pattern.Ixion
I just removed 5 files of redundant methods. Just great.Drysalter

© 2022 - 2024 — McMap. All rights reserved.