I pass struct into a function as interface{}
. Then inside I work with it using reflect
to get the struct attributes. Here's the code:
func (db *DB) Migrate(domain ...interface{}) {
// statement := "CREATE TABLE IF NOT EXISTS %s (%s, %s, %s, %s, %s)"
for _,i := range domain {
params := BindStruct(&i)
statement := CreateStatement("create", len(params))
_,err := db.Exec(fmt.Sprintf(statement, params...))
if err != nil {
log.Fatal("Error migrating database schema - ", err)
break
}
}
}
func BindStruct(domain interface{}) (params []interface{}) {
tableName := reflect.TypeOf(domain).Elem().Name()
params = append(params, tableName)
val := reflect.ValueOf(domain).Elem()
for i:=0; i < val.NumField(); i++ {
field := val.Type().Field(i)
tag := field.Tag
fieldName := field.Name
fieldType := tag.Get("sql_type")
fieldTags := tag.Get("sql_tag")
paramstring := fieldName + " " + fieldType + " " + fieldTags
params = append(params, paramstring)
}
return params
}
I got an error on the line for i:=0; i < val.NumField(); i++ {
in BindStruct
function. The error message is:
panic: reflect: call of reflect.Value.NumField on interface Value
If I remove &
from params := BindStruct(&i)
becoming params := BindStruct(i)
in Migrate function, I get this error:
panic: runtime error: invalid memory address or nil pointer
dereference[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4ff457]
What gives?
Interfaces store type information when assigned a value. Reflection is a method of examining type and value information at runtime. Go implements reflection with the reflect package which provides types and methods for inspecting portions of the interface structure and even modifying values at runtime.
Reflection is the ability of a program to introspect and analyze its structure during run-time. In Go language, reflection is primarily carried out with types. The reflect package offers all the required APIs/Methods for this purpose. Reflection is often termed as a method of metaprogramming.
You can use reflection to get the type of a variable var with the function call varType := reflect. TypeOf(var). This returns a variable of type reflect. Type, which has methods with all sorts of information about the type that defines the variable that was passed in.
The reflect. Indirect() Function in Golang is used to get the value that v points to, i.e., If v is a nil pointer, Indirect returns a zero Value. If v is not a pointer, Indirect returns v. To access this function, one needs to imports the reflect package in the program.
When you call BindStruct(&i)
you are passing a pointer to an interface. so this line:
val := reflect.ValueOf(domain).Elem()
will set val to a reflect.Value representing your interface because reflect.ValueOf(domain)
gets a pointer then .Elem()
resolves the interface - which results in your first error as it is indeed an interface value (and they don't have fields):
panic: reflect: call of reflect.Value.NumField on interface Value
So, calling params := BindStruct(i)
would always be correct as you need to pass the actual interface in not a pointer to it.
You don't make clear what the underlying data types being passed into Migrate()
are - are they values or pointers? It's important to know, e.g to inspect struct tags using reflection we need to get back to the struct values type, so the chain goes:
interface -> (pointer ?) -> value -> type
I suspect you are using values as if interface was a value then the line:
val := reflect.ValueOf(domain).Elem()
would be expected to panic since reflect.ValueOf(domain)
would resolve the value and then .Elem()
would try to de-refrence a value.
To be on the safe side we will check the Kind() of the incoming value to make sure we have a struct:
https://play.golang.org/p/6lPOwTd1Q0O
func BindStruct(domain interface{}) (params []interface{}) {
val := reflect.ValueOf(domain) // could be any underlying type
// if its a pointer, resolve its value
if val.Kind() == reflect.Ptr {
val = reflect.Indirect(val)
}
// should double check we now have a struct (could still be anything)
if val.Kind() != reflect.Struct {
log.Fatal("unexpected type")
}
// now we grab our values as before (note: I assume table name should come from the struct type)
structType := val.Type()
tableName := structType.Name()
params = append(params, tableName)
for i:=0; i < structType.NumField(); i++ {
field := structType.Field(i)
tag := field.Tag
fieldName := field.Name
fieldType := tag.Get("sql_type")
fieldTags := tag.Get("sql_tag")
paramstring := fieldName + " " + fieldType + " " + fieldTags
params = append(params, paramstring)
}
return params
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With