Golang reflect if initialized struct has member method or fields

12 April, 2018

In Go it is possible to check if any pointer to an initialized struct has a given method or field. The use of this is to make sure that you can call a given method, or access a given field without receiving a runtime panic.

Check if struct has method

// Reflect if an interface is either a struct or a pointer to a struct
// and has the defined member method. If error is nil, it means
// the MethodName is accessible with reflect.
func ReflectStructMethod(Iface interface{}, MethodName string) error {
	ValueIface := reflect.ValueOf(Iface)

	// Check if the passed interface is a pointer
	if ValueIface.Type().Kind() != reflect.Ptr {
		// Create a new type of Iface, so we have a pointer to work with
		ValueIface = reflect.New(reflect.TypeOf(Iface))
	}

	// Get the method by name
	Method := ValueIface.MethodByName(MethodName)
	if !Method.IsValid() {
		return fmt.Errorf("Couldn't find method `%s` in interface `%s`, is it Exported?", MethodName, ValueIface.Type())
	}
	return nil
}

With reflect it is only possible to find exported methods of a struct.  

Check if struct has field

// Reflect if an interface is either a struct or a pointer to a struct
// and has the defined member field, if error is nil, the given
// FieldName exists and is accessible with reflect.
func ReflectStructField(Iface interface{}, FieldName string) error {
	ValueIface := reflect.ValueOf(Iface)

	// Check if the passed interface is a pointer
	if ValueIface.Type().Kind() != reflect.Ptr {
		// Create a new type of Iface's Type, so we have a pointer to work with
		ValueIface = reflect.New(reflect.TypeOf(Iface))
	}

	// 'dereference' with Elem() and get the field by name
	Field := ValueIface.Elem().FieldByName(FieldName)
	if !Field.IsValid() {
		return fmt.Errorf("Interface `%s` does not have the field `%s`", ValueIface.Type(), FieldName)
	}
	return nil
}

However with reflect, unexported fields are accessible compared to unexported methods.

Note if you are planning to call or modify members

If you passed a struct value (not a pointer to a struct) as the first parameter, a new struct with the type of the passed struct will be created, thus none of the values that Iface contained will be in the new struct. Keep this in mind if you are planning to call a method with this code.

If you want to keep the values you can Set() them. However the original Iface will not be mutated, only the new struct.

Mock example

Go playground

package main

import (
	"fmt"
	"reflect"
)

// Some mock type
type Book struct {
	CurrentPage int
	maxPages    int
}

// Go to the next page, unless we are at the
// end of the book, reset the page.
func (b *Book) NextPage() int {
	if b.CurrentPage == 0 {
		// Initialize to 0
		b.reset()
	}
	if b.CurrentPage >= b.maxPages {
		b.reset()
	} else {
		b.CurrentPage++
	}
	return b.CurrentPage
}

// Set the beginning of the book back to page 1
func (b *Book) reset() {
	b.CurrentPage = 1
}

func main() {
	var err error
	SomeBook := Book{CurrentPage: 1, maxPages: 10}
	PtrToSomeBook := &SomeBook

	testMethods := []string{"NextPage", "String", "reset"}
	testFields := []string{"SomeField", "CurrentPage", "maxPages"}

	// Test SomeBook
	fmt.Println("Test `SomeBook := Book{CurrentPage:1, maxPages:10}`")
	fmt.Println("\tMethods")
	for _, v := range testMethods {
		err = ReflectStructMethod(SomeBook, v)
		fmt.Print("\t\t")
		if err != nil {
			fmt.Println(err)
		} else {
			fmt.Printf("Method `%s` found\n", v)
		}
	}
	fmt.Println("\tFields")
	for _, v := range testFields {
		err = ReflectStructField(SomeBook, v)
		fmt.Print("\t\t")
		if err != nil {
			fmt.Println(err)
		} else {
			fmt.Printf("Method `%s` found\n", v)
		}
	}

	// Test PtrToSomeBook
	fmt.Println("Test `PtrToSomeBook := &SomeBook`")
	fmt.Println("\tMethods")
	for _, v := range testMethods {
		err = ReflectStructMethod(PtrToSomeBook, v)
		// Some tabs
		fmt.Print("\t\t")
		if err != nil {
			fmt.Println(err)
		} else {
			fmt.Printf("Method `%s` found\n", v)
		}
	}
	fmt.Println("\tFields")
	for _, v := range testFields {
		err = ReflectStructField(PtrToSomeBook, v)
		// Some tabs
		fmt.Print("\t\t")
		if err != nil {
			fmt.Println(err)
		} else {
			fmt.Printf("Field `%s` found\n", v)
		}
	}
}

// Reflect if an interface is either a struct or a pointer to a struct
// and has the defined member method. If error is nil, it means
// the MethodName is accessible with reflect.
func ReflectStructMethod(Iface interface{}, MethodName string) error {
	ValueIface := reflect.ValueOf(Iface)

	// Check if the passed interface is a pointer
	if ValueIface.Type().Kind() != reflect.Ptr {
		// Create a new type of Iface, so we have a pointer to work with
		ValueIface = reflect.New(reflect.TypeOf(Iface))
	}

	// Get the method by name
	Method := ValueIface.MethodByName(MethodName)
	if !Method.IsValid() {

		return fmt.Errorf("Couldn't find method `%s` in interface `%s`, is it Exported?", MethodName, ValueIface.Type())
	}
	return nil
}

// Reflect if an interface is either a struct or a pointer to a struct
// and has the defined member field, if error is nil, the given
// FieldName exists and is accessible with reflect.
func ReflectStructField(Iface interface{}, FieldName string) error {
	ValueIface := reflect.ValueOf(Iface)

	// Check if the passed interface is a pointer
	if ValueIface.Type().Kind() != reflect.Ptr {
		// Create a new type of Iface's Type, so we have a pointer to work with
		ValueIface = reflect.New(reflect.TypeOf(Iface))
	}

	// 'dereference' with Elem() and get the field by name
	Field := ValueIface.Elem().FieldByName(FieldName)
	if !Field.IsValid() {
		return fmt.Errorf("Interface `%s` does not have the field `%s`", ValueIface.Type(), FieldName)
	}
	return nil
}