Reputation: 6099
I have a struct that looks like this:
type BetaKey struct {
Id int64 `json:"-"`
IssuerId int64 `json:"-" db:"issuer_id"`
SubjectEmail string `json:"-" db:"subject_email"`
IssuedTime int64 `json:"-" db:"issued_time"`
ExpiryTime int64 `json:"expiryTime" db:"expiry_time"`
Betakey string `json:"accessKey"`
Description string `json:"description"`
}
And within the same package I have a function that returns a slice of BetaKey
:
func buildResults(query string) ([]BetaKey, error) {
results := []BetaKey{}
rows, err := sql.DB.Queryx(query)
if err != nil {
return results, err
}
defer rows.Close()
for rows.Next() {
var bk BetaKey
err := rows.StructScan(&bk)
if err != nil {
return results, err
}
results = append(results, bk)
}
err = rows.Err()
if err != nil {
return results, err
}
return results, nil
}
Is it possible for me to rewrite this function so that it takes in a query string but also a type of BetaKey
as interface{}
, and returns a slice of interface{}
so that I can reuse the code instead of copy pasting this into every package because it is literally the same but the only difference is the name of the struct that changes.
Is this possible? And also is this advised? If not, then why?
Upvotes: 2
Views: 140
Reputation: 417642
Generics could be used to implement such thing, but Go does not support generics. To do what you want in Go, you need to use reflection.
You may change your function to take 1 additional parameter, a reflect.Type
for example which designates type of values the individual rows should be loaded into.
Then you can use reflect.New()
to create a new value of this type and acquire a pointer to it. You can use Value.Interface()
to obtain the pointer value as a type of interface{}
from the reflect.Value
value. This interface{}
wrapping the pointer can now be passed to Rows.StructScan()
.
And if you want the result slice to hold non-pointer values, you can use reflect.Indirect()
to get the pointed value (and another reflect.Interface()
to extract the struct value as an interface{}
).
Example code:
func buildResults(query string, t reflect.Type) ([]interface{}, error) {
results := []interface{}{}
rows, err := sql.DB.Queryx(query)
if err != nil {
return results, err
}
defer rows.Close()
for rows.Next() {
val := reflect.New(t)
err := rows.StructScan(val.Interface())
if err != nil {
return results, err
}
i_ := reflect.Indirect(val)
result = append(result, i_.Interface())
}
err = rows.Err()
if err != nil {
return results, err
}
return results, nil
}
The heart of it is the for
block:
val := reflect.New(t) // A pointer to a new value (of type t)
err := rows.StructScan(val.Interface()) // Pass the pointer to StructScan
if err != nil {
return results, err
}
i_ := reflect.Indirect(val) // Dereference the pointer
result = append(result, i_.Interface()) // And add the non-pointer to the result slice
Here's how you can test it:
type BetaKey struct {
Id string
Name string
}
type AlphaKey struct {
Id string
Source string
}
r, err := buildResults("", reflect.TypeOf(AlphaKey{}))
fmt.Printf("%T %+v %v\n", r[0], r, err)
r, err = buildResults("", reflect.TypeOf(BetaKey{}))
fmt.Printf("%T %+v %v\n", r[0], r, err)
Output:
main.AlphaKey [{Id:aa Source:asource} {Id:aa Source:asource} {Id:aa Source:asource}] <nil>
main.BetaKey [{Id:aa Name:aname} {Id:aa Name:aname} {Id:aa Name:aname}] <nil>
Try it on the Go Playground.
Notes:
The above solution will return a value of type of []interface{}
whose elements will be of static type interface{}
and their dynamic type will be the one specified by the reflect.Type
argument. So for example if you call it with type:
bt := reflect.TypeOf(BetaKey{})
The values in the result slice will have dynamic type BetaKey
so you can safely type assert them like this:
results, err := buildResults("some query", bt)
if err == nil {
for _, v := range results {
key := v.(BetaKey)
// key is of type BetaKey, you may use it like so
}
} else {
// handle error
}
To learn more about reflection, read the blog post:
The Go Blog: The Laws of Reflection
Upvotes: 1
Reputation: 2668
I write a little example using json, instead of sql rows. You can try to develop from this code :
package main
import (
"fmt"
"reflect"
"encoding/json"
)
type A struct {
AField int `json:"a"`
}
type B struct {
BField string `json:"b"`
}
func build(str string, typ reflect.Type) interface{} {
results := reflect.MakeSlice(reflect.SliceOf(typ), 0, 10)
for i:=0; i < 5; i++ {
res := reflect.New(typ)
json.Unmarshal([]byte(str), res.Interface())
results = reflect.Append(results, res.Elem())
}
return results.Interface();
}
func main() {
a := build("{ \"a\": 15 }", reflect.TypeOf(&A{}))
fmt.Printf("%T : %V\n", a, a)
b := build("{ \"b\": \"my string\" }", reflect.TypeOf(&B{}))
fmt.Printf("%T : %V\n", b, b)
}
Upvotes: 1