Call a Struct and its Method by name in Go?
Asked Answered
C

5

41

I have found a function call MethodByName() here http://golang.org/pkg/reflect/#Value.MethodByName but it's not exactly what I want! (maybe because I don't know how to use it ... I cannot find any example with it). What I want is:

type MyStruct struct {
//some feilds here
} 
func (p *MyStruct) MyMethod { 
    println("My statement."); 
} 

CallFunc("MyStruct", "MyMethod"); 
//print out "My statement." 

So I guess, first I need something like StructByName() and after that use it for MethodByName(), is that right!?

Cia answered 12/11, 2011 at 9:26 Comment(4)
since MyMethod is a method of *MyStruct, I believe you would at least need an instance of *MyStruct to cal MyMethod with. Maybe it's assumed that CallFunc creates a zeroed instaces of MyStruct?Werewolf
the hard thing is I don't know the type of the Struct yet!Cia
Here if you are expecting the ` CallFunc("MyStruct", "MyMethod"); ` to instanciate the struct and execute the function which need to be an impure function to make sense then only reason you have the * there is to not have a pass by value of huge struct.So in essence the struct would work just as a namespaceConsonant
I have posted a number of helpful examples on using reflection.Orianna
E
72

To call a method on an object, first use reflect.ValueOf. Then find the method by name, and then finally call the found method. For example:

package main

import "fmt"
import "reflect"

type T struct {}

func (t *T) Foo() {
    fmt.Println("foo")
}

func main() {
    var t T
    reflect.ValueOf(&t).MethodByName("Foo").Call([]reflect.Value{})
}
Excipient answered 12/11, 2011 at 17:49 Comment(5)
The issue in my case Is I cant not declare t is typed T, its must be some how I can declare t typed T by the name of T is string "T".Cia
@nvcnvn: I would suggest to match the name against the string "T" somewhere in your code and create a value of type T if the name matched. If it matches some other type "U", create a value of type U. The values can be freely passed around as interface{}.Excipient
Thank you @Atom, what if the method Foo has any arguments? And has any return values?Biliary
``` func callMethod(methodName string, reflectValue reflect.Value) { // Only get address from non-pointer if reflectValue.CanAddr() && reflectValue.Kind() != reflect.Ptr { reflectValue = reflectValue.Addr() } if methodValue := reflectValue.MethodByName(methodName);methodValue.IsValid() { switch method := methodValue.Interface().(type) { case func(): method() case func() error: log.LogError(method()) .............. } } ```Consonant
this just outputs [<float64 Value>] rather than the value returned by the function for meGyimah
L
36
type YourT1 struct {}
func (y YourT1) MethodBar() {
    //do something
}

type YourT2 struct {}
func (y YourT2) MethodFoo(i int, oo string) {
    //do something
}

func Invoke(any interface{}, name string, args... interface{}) {
    inputs := make([]reflect.Value, len(args))
    for i, _ := range args {
        inputs[i] = reflect.ValueOf(args[i])
    }
    reflect.ValueOf(any).MethodByName(name).Call(inputs)
}

func main() {
     Invoke(YourT2{}, "MethodFoo", 10, "abc")
     Invoke(YourT1{}, "MethodBar")
}

Really the code needs to check the method's input number and even whether the method itself exists.

You can reference this http://gowalker.org/reflect#Type

  1. Check "any" is a struct type
  2. Check "any" has "name" method
  3. Check the number of method "name" input parameters is equal the length of args
  4. Implement ret by reflect.Value.Interface()

and be careful the Ptr type;

Or you can use SomeInterface{} instead of directly using interface{} to ensure this "any" type, like this:

type Shape interface {
    Area() float64  //some method to ensure any is an Shape type.
}

func Invoke(s Shape, name string, inputs...interface{}) []interface{} {
}

so this is OK

color := Invoke(Circle{}, "GetColor")[0].(Color)

but

Invoke(NotAShape{}, "ForBar")

can't be compiled because NotAnShape isn't a Shape.

If you can't be sure about the first type at compile time, you can build a map to store all possible type, like this:

map[string]reflect.Value{
    "YourT1" : reflect.ValueOf(YourT1{})
    "YourT2" : reflect.ValueOf(YourT2{})
    "Circle" : reflect.ValueOf(Cirlce{}) // or reflect.ValueOf(&Circle{})
}  
Lectionary answered 1/11, 2013 at 5:57 Comment(4)
thank you so much for the code. How about the method has any return values?Biliary
the return values is more bother becaus you have to manually convert the reflect.Value to your excepted value type. gowalker.org/reflect#Value_Call will return an array of reflect.Values as return values. reflect.Value has the method Interface which will return the an interface.Lectionary
snyh, can you explain it more? I'll be very grateful if you explain how can i convert []reflect.Value here to error, because my function has this signature func doSmth() error {...}Veliger
@Veliger gowalker.org/reflect#Value_Interface err := fmt.Errorf("a error example") vv := reflect.ValueOf(err) s, ok := vv.Interface().(error) if ok { fmt.Println(s) } `Lectionary
I
3

gist invoke struct method with error handling

// Invoke - firstResult, err := Invoke(AnyStructInterface, MethodName, Params...)
func invoke(any interface{}, name string, args ...interface{}) (reflect.Value, error) {
    method := reflect.ValueOf(any).MethodByName(name)
    methodType := method.Type()
    numIn := methodType.NumIn()
    if numIn > len(args) {
        return reflect.ValueOf(nil), fmt.Errorf("Method %s must have minimum %d params. Have %d", name, numIn, len(args))
    }
    if numIn != len(args) && !methodType.IsVariadic() {
        return reflect.ValueOf(nil), fmt.Errorf("Method %s must have %d params. Have %d", name, numIn, len(args))
    }
    in := make([]reflect.Value, len(args))
    for i := 0; i < len(args); i++ {
        var inType reflect.Type
        if methodType.IsVariadic() && i >= numIn-1 {
            inType = methodType.In(numIn - 1).Elem()
        } else {
            inType = methodType.In(i)
        }
        argValue := reflect.ValueOf(args[i])
        if !argValue.IsValid() {
            return reflect.ValueOf(nil), fmt.Errorf("Method %s. Param[%d] must be %s. Have %s", name, i, inType, argValue.String())
        }
        argType := argValue.Type()
        if argType.ConvertibleTo(inType) {
            in[i] = argValue.Convert(inType)
        } else {
            return reflect.ValueOf(nil), fmt.Errorf("Method %s. Param[%d] must be %s. Have %s", name, i, inType, argType)
        }
    }
    return method.Call(in)[0], nil
}
Inverness answered 14/11, 2018 at 4:22 Comment(0)
N
1

This is great. I added the return value example for it.

package main
import ("fmt";"math/rand";"reflect";"time")

// Invoke call the struct method and store the return values in a []reflect.Value
// return value example
// assume return value: (int, time.Time, error)
// var r []reflect.Value
// => r[0].interface() // for int
// => r[1].interface() // for time.Time
// => r[2].interface() // for error
// and so on.
func Invoke(obj any, name string, args ...any) []reflect.Value {
    inputs := make([]reflect.Value, len(args))
    for i, _ := range args {
        inputs[i] = reflect.ValueOf(args[i])
    }
    return reflect.ValueOf(obj).MethodByName(name).Call(inputs)
}

type Score struct{}

func (s Score) IsExcellent(score int) (bool, error) {
    if score < 0 {
        return false, fmt.Errorf("invalid score")
    }
    if score > 90 {
        return true, nil
    }
    return false, nil
}

func (s Score) Generate(min, max int) (int, time.Time) {
    rand.Seed(time.Now().UnixNano())
    return rand.Intn(max-min) + min, time.Now()
}

func main() {
    s := Score{}
    values1 := Invoke(s, "IsExcellent", 95)
    values2 := Invoke(s, "IsExcellent", -5)
    for _, values := range [][]reflect.Value{values1, values2} {
        if len(values) > 0 {
            err := values[1].Interface()
            if err != nil {
                fmt.Println(err.(error))
                continue
            }
            fmt.Println(values[0].Bool())
        }
    }
    values := Invoke(Score{}, "Generate", 0, 101)
    randNumber := values[0].Int()
    createTime := values[1].Interface().(time.Time) // It's better to check `values[1].Interface()` equal to nil first before you do assertion.
    fmt.Println(randNumber, createTime)
}

go playground

Nazarius answered 9/5, 2022 at 5:16 Comment(0)
A
0
package main

import (
    "fmt"
    "reflect"
)

type Log struct {
    Path  string
    Level string
}

func (l *Log) Conversion(i interface{}) {

    if data, ok := i.(*Log); ok {
        if data != nil {
            if len(data.Path) > 0 {
                l.Path = data.Path
            }
            if len(data.Level) > 0 {
                l.Level = data.Level
            }
        }
    }
}

type Storage struct {
    Type       string
    ServerList []string
}

func (s *Storage) Conversion(i interface{}) {

   if data, ok := i.(*Storage); ok {
        if data != nil {
            if len(data.Type) > 0 {
                s.Type = data.Type
            }
        }
    }
}

type Server struct {
    LogConfig     *Log
    StorageConfig *Storage
}

func main() {
    def := Server{
        LogConfig: &Log{
            Path:  "/your/old/log/path/",
            Level: "info",
        },
        StorageConfig: &Storage{
            Type:       "zookeeper",
            ServerList: []string{"127.0.0.1:2181"},
        },
    }

    fmt.Println(def)
    cur := Server{
        LogConfig: &Log{
            Path:  "/your/new/log/path/",
            Level: "debug",
        },
        StorageConfig: &Storage{
            Type:       "etcd",
            ServerList: []string{"127.0.0.1:2379"},
        },
    }

    fmt.Println(cur)

    defV := reflect.ValueOf(def)
    curV := reflect.ValueOf(cur)
    for k := 0; k < defV.NumField(); k++ {
        in := make([]reflect.Value, 1)
        in[0] = reflect.ValueOf(curV.Field(k).Interface())
        defV.Field(k).MethodByName("Conversion").Call(in)
    }

    fmt.Println(def.LogConfig)
    fmt.Println(def.StorageConfig)
}
Anagoge answered 12/11, 2011 at 9:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.