Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reflect on struct passed into interface{} function parameter

Tags:

reflection

go

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?

like image 733
blue panther Avatar asked Mar 13 '18 01:03

blue panther


People also ask

What is reflection interface?

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.

What is reflect in Go?

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.

How do you use reflection in Go?

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.

What is reflect indirect?

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.


1 Answers

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
}
like image 169
SwiftD Avatar answered Nov 27 '22 15:11

SwiftD