英文:
How to pass a dynamically created struct as non-pointer object in Go
问题
我试图动态地将参数传递给URL路由处理函数。我认为可以使用反射包将URL中的值映射到具有一个参数的函数,该参数恰好是一个匿名结构体。我已经创建了要传递给处理程序函数的结构体,但最终它变成了指向结构体的指针。如果我将处理程序函数的签名更改为期望一个指针,那么创建的结构体最终会变成指向指针的指针,我想。无论如何,以下是代码(紧随其后的是恐慌):
英文:
I'm trying to dynamically pass parameters to a URL route handler function. I thought it would be possible to use the reflection package to convert a map of values from the URL to the function that has one parameter that happens to be an anonymous struct. I've gotten as far as creating the struct to pass to the handler function but it ends up being a pointer to the struct. If I change the handler function's signature to expect a pointer the created struct ends up being a pointer to a pointer, I think. At any rate, here's the code (the panic follows):
Link: http://play.golang.org/p/vt_wNY1f08
package main
import (
"errors"
"fmt"
"net/http"
"reflect"
"strconv"
"github.com/gorilla/mux"
)
func mapToStruct(obj interface{}, mapping map[string]string) error {
dataStruct := reflect.Indirect(reflect.ValueOf(obj))
if dataStruct.Kind() != reflect.Struct {
return errors.New("expected a pointer to a struct")
}
for key, data := range mapping {
structField := dataStruct.FieldByName(key)
if !structField.CanSet() {
fmt.Println("Can't set")
continue
}
var v interface{}
switch structField.Type().Kind() {
case reflect.Slice:
v = data
case reflect.String:
v = string(data)
case reflect.Bool:
v = string(data) == "1"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
x, err := strconv.Atoi(string(data))
if err != nil {
return errors.New("arg " + key + " as int: " + err.Error())
}
v = x
case reflect.Int64:
x, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return errors.New("arg " + key + " as int: " + err.Error())
}
v = x
case reflect.Float32, reflect.Float64:
x, err := strconv.ParseFloat(string(data), 64)
if err != nil {
return errors.New("arg " + key + " as float64: " + err.Error())
}
v = x
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
x, err := strconv.ParseUint(string(data), 10, 64)
if err != nil {
return errors.New("arg " + key + " as int: " + err.Error())
}
v = x
default:
return errors.New("unsupported type in Scan: " + reflect.TypeOf(v).String())
}
structField.Set(reflect.ValueOf(v))
}
return nil
}
type RouteHandler struct {
Handler interface{}
}
func (h RouteHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
t := reflect.TypeOf(h.Handler)
handlerArgs := reflect.New(t.In(0)).Interface()
if err := mapToStruct(handlerArgs, mux.Vars(req)); err != nil {
panic(fmt.Sprintf("Error converting params"))
}
f := reflect.ValueOf(h.Handler)
args := []reflect.Value{reflect.ValueOf(handlerArgs)}
f.Call(args)
fmt.Fprint(w, "Hello World")
}
type App struct {
Router mux.Router
}
func (app *App) Run(bind string, port int) {
bind_to := fmt.Sprintf("%s:%d", bind, port)
http.Handle("/", &app.Router)
http.ListenAndServe(bind_to, &app.Router)
}
func (app *App) Route(pat string, h interface{}) {
app.Router.Handle(pat, RouteHandler{Handler:h})
}
func home(args struct{Category string}) {
fmt.Println("home", args.Category)
}
func main() {
app := &App{}
app.Route("/products/{Category}", home)
app.Run("0.0.0.0", 8080)
}
Panic:
2013/03/28 18:48:43 http: panic serving 127.0.0.1:51204: reflect: Call using *struct { Category string } as type struct { Category string }
/usr/local/Cellar/go/1.0.3/src/pkg/net/http/server.go:589 (0x3fb66)
_func_004: buf.Write(debug.Stack())
/usr/local/Cellar/go/1.0.3/src/pkg/runtime/proc.c:1443 (0x11cdb)
panic: reflect·call(d->fn, d->args, d->siz);
/usr/local/Cellar/go/1.0.3/src/pkg/reflect/value.go:428 (0x484ba)
Value.call: panic("reflect: " + method + " using " + xt.String() + " as type " + targ.String())
/usr/local/Cellar/go/1.0.3/src/pkg/reflect/value.go:334 (0x47c3a)
Value.Call: return v.call("Call", in)
/Users/matt/Workspaces/Go/src/pants/pants.go:86 (0x2f36)
RouteHandler.ServeHTTP: f.Call(args)
/Users/matt/Workspaces/Go/src/pants/pants.go:1 (0x347c)
(*RouteHandler).ServeHTTP: package main
/Users/matt/Workspaces/Go/src/github.com/gorilla/mux/mux.go:86 (0x5a699)
com/gorilla/mux.(*Router).ServeHTTP: handler.ServeHTTP(w, req)
/usr/local/Cellar/go/1.0.3/src/pkg/net/http/server.go:669 (0x337b6)
(*conn).serve: handler.ServeHTTP(w, w.req)
/usr/local/Cellar/go/1.0.3/src/pkg/runtime/proc.c:271 (0xfde1)
goexit: runtime·goexit(void)
答案1
得分: 3
在你的reflect.Value对象上调用Elem()。
引用自《反射定律》文章:
> 要获取p指向的内容,我们调用Value的Elem方法,该方法通过指针进行间接引用。
英文:
Call Elem() on your reflect.Value object.
Quoting from The Laws of Reflection article:
> To get to what p points to, we call the Elem method of Value, which indirects through the pointer
答案2
得分: 0
请注意,reflect.New()
创建的是一个指向您传递的类型的值的指针。因此,在下一行中:
handlerArgs := reflect.New(t.In(0)).Interface()
handlerArgs
将是一个指向类型为 t.In(0)
的结构体的指针。您需要解引用该指针,以获得适合传递给函数的值。
我建议以下操作:
-
将
handlerArgs
声明为*reflect.Value
,以获取实际的结构体值:handlerArgs := reflect.New(t.In(0)).Elem()
-
将
mapToStruct
接受这样的*reflect.Value
,而不是包含指向结构体的指针的interface{}
(毕竟它是您反射代码的辅助函数)。 -
在
Call()
调用中直接使用handlerArgs
作为函数参数之一。
英文:
Note that reflect.New()
creates a pointer to a value of the type you pass it. So on the following line:
handlerArgs := reflect.New(t.In(0)).Interface()
handlerArgs
will be a pointer to a struct of type t.In(0)
. You will need to dereference that pointer to have a value suitable to pass to the function.
I'd suggest the following:
-
Make
hanlerArgs
a*reflect.Value
for the actual struct value:handlerArgs := reflect.New(t.In(0)).Elem()
-
Make
mapToStruct
take such a*reflect.Value
instead of aninterface{}
holding a pointer to a struct (it is a helper for your reflection code, after all). -
Use
handlerArgs
directly as one of the function arguments for the `Call() invocation.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论