英文:
go reflection: get correct struct type of interface
问题
考虑以下代码:
type myStruct struct {
    Foo string `json:"foo"`
}
func main() {
    somelibrary.DoThing(func(thing myStruct) {
        // myStruct should contain unmarshaled JSON
        // provided by somelibrary
        fmt.Printf("%v\n", thing)
    })
}
我是Go的新手,所以我担心这可能不是惯用的代码。我想实现somelibrary.DoThing,以便它可以通过反射从函数参数中正确推断出结构体类型,如果可能的话。以下是我的实现:
const jsonData = []byte(`{"foo": "bar"}`)
func DoThing(fn interface{}) {
    // 获取函数的第一个参数
    firstArg := reflect.TypeOf(fn).In(0)
    structPtr := reflect.New(firstArg)
    // 转换为接口类型
    // 注意,我不能将其断言为.(myStruct)类型
    instance := structPtr.Elem().Interface()
    // 反序列化JSON
    json.Unmarshal(jsonData, &instance)
    // 调用函数
    vfn := reflect.ValueOf(fn)
    vfn.Call([]reflect.Value{reflect.ValueOf(instance)})
}
在不事先知道结构体类型的情况下,json.Unmarshal默认将instance解析为map[string]interface{},因此在调用vfn.Call(...)时会出现恐慌:
panic: reflect: Call using map[string]interface {} as type main.myStruct
是否可以将instance接口转换为正确的类型?换句话说,我是否可以通过传递一个字符串(或使用某种反射方法)来进行类型断言,而不是将类型作为程序的符号可用?
英文:
Consider this:
type myStruct struct {
	Foo string `json:"foo"`
}
func main() {
	somelibrary.DoThing(func(thing myStruct) {
		// myStruct should contain unmarshaled JSON
        // provided by somelibrary
		fmt.Printf("%v\n", thing)
	})
}
I'm new to Go, so I fear this might not be idiomatic code. I'd like to implement somelibrary.DoThing so it correctly infers the struct type from the function argument via reflection, if it's possible. Here's what I have:
const jsonData := []byte{`{"foo": "bar"}`}
func DoThing(fn interface{}) {
	// Get first arg of the function
	firstArg := reflect.TypeOf(fn).In(0)
	structPtr := reflect.New(firstArg)
	// Convert to Interface
    // Note that I can't assert this to .(myStruct) type
	instance := structPtr.Elem().Interface()
    // Unmarshal the JSON
	json.Unmarshal(jsonData, &instance)
    // Call the function
	vfn := reflect.ValueOf(fn)
	vfn.Call([]reflect.Value{reflect.ValueOf(instance)})
}
Without knowing the struct type beforehand, json.Unmarshal just assumes that instance is map[string]interface{}, so I get a panic when calling vfn.Call(...):
panic: reflect: Call using map[string]interface {} as type main.myStruct
Is it possible to convert the instance interface into the correct type? In other words, can I do type type assertion by passing a string (or using some reflection method) instead of having the type available to the program as a symbol?
答案1
得分: 3
是的,这是可能的。这是你更新后的代码:
func DoThing(fn interface{}) {
    // 获取函数的第一个参数
    firstArg := reflect.TypeOf(fn).In(0)
    // 获取第一个函数参数的指针
    structPtr := reflect.New(firstArg)
    // 转换为接口类型
    // 注意,我不能断言为 .(myStruct) 类型
    instance := structPtr.Interface()
    // 反序列化 JSON
    json.Unmarshal(jsonData, instance)
    // 调用函数
    vfn := reflect.ValueOf(fn)
    vfn.Call([]reflect.Value{structPtr.Elem()})
}
所做的更改:
- 将 
structPtr(一个指针)传递给json.Unmarshal;如果传递一个值,你将看不到更改。 - 在传递给 
json.Unmarshal时,删除对instance取地址的操作;通常没有理由将接口的指针 - 在调用 
fn时,使用structPtr而不是instance 
https://play.golang.org/p/POmOyQBJYC
英文:
Yes, this is possible. Here is your code updated:
func DoThing(fn interface{}) {
	// Get first arg of the function
	firstArg := reflect.TypeOf(fn).In(0)
	// Get the PtrTo to the first function parameter
	structPtr := reflect.New(firstArg)
	// Convert to Interface
	// Note that I can't assert this to .(myStruct) type
	instance := structPtr.Interface()
	// Unmarshal the JSON
	json.Unmarshal(jsonData, instance)
	// Call the function
	vfn := reflect.ValueOf(fn)
	vfn.Call([]reflect.Value{structPtr.Elem()})
}
Changes made:
- Pass 
structPtr(a pointer) tojson.Unmarshal; pass a value and you will not see the changes - Remove taking the address of 
instancewhen passing tojson.Unmarshal; there is usually never a good reason to have a pointer to an interface - Use 
structPtrinstead ofinstancewhen callingfn 
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论