Golang反射无法识别来自映射成员的标签。

huangapple go评论124阅读模式
英文:

golang reflect cannot recognize tag from map members

问题

我想使用反射(reflect)提取结构体的映射成员的标签,但是我发现如果从MapIndex中检索成员的值,它的类型将被识别为"*interface{}",因此所有类型信息都丢失了,更不用说反射可以提取详细信息了。

在运行后,我得到了以下错误:

  1. Starting: C:\Users\Mento\go\bin\dlv.exe dap --check-go-version=false --listen=127.0.0.1:50308 from d:\Coding\Golang\demo
  2. DAP server listening at: 127.0.0.1:50308
  3. Key: Sen, Value: {Sen}, value pointer 0xc000044230
  4. Traversal tag with obj: type *main.Student, value &{Sen}
  5. Tag name Sname, value MyTag:"student-name"
  6. Key: Sen, Value: {Sen}, value pointer 0xc0000442c0
  7. Traversal tag with obj: type *interface {}, value 0xc0000442c0
  8. panic: reflect: NumField of non-struct type interface {}
  9. goroutine 1 [running]:
  10. reflect.(*rtype).NumField(0xec2d20)
  11. C:/Program Files/Go/src/reflect/type.go:1015 +0xc8
  12. main.traversalTag({0xebd9e0, 0xc0000442c0})
  13. d:/Coding/Golang/demo/demo.go:31 +0x1cb
  14. main.tryMapWithReflect({0xec3d40, 0xc00007a480})
  15. d:/Coding/Golang/demo/demo.go:48 +0x2c9
  16. main.main()
  17. d:/Coding/Golang/demo/demo.go:54 +0x38
  18. Process 8716 has exited with status 2
  19. dlv dap (11448) exited with code: 0

有人可以提示如何获取具有原始类型信息的映射成员的指针吗?

谢谢!
Mento

英文:

I want to extract struct's map members' tag with reflect, while I found if retrieve member's value from MapIndex, the type of it will be recognized as "*interface{}" and hence all type information are lost, no mention reflect can extract detail information.

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type Student struct {
  7. Sname string `MyTag:"student-name"`
  8. }
  9. type Teacher struct {
  10. Name string `MyTag:"teacher-name"`
  11. Students map[string]Student `MyTag:"teacher-students"`
  12. }
  13. var sam = Teacher{
  14. Name: "Sam",
  15. Students: map[string]Student{
  16. "Sen": {
  17. Sname: "Sen",
  18. },
  19. },
  20. }
  21. func traversalTag(obj interface{}) {
  22. theType := reflect.TypeOf(obj)
  23. fmt.Printf("Traversal tag with obj: type %v, value %v\n", theType.String(), obj)
  24. elem := reflect.TypeOf(obj).Elem()
  25. for i := 0; i < elem.NumField(); i++ {
  26. fmt.Printf("Tag name %s, value %s\n", elem.Field(i).Name, elem.Field(i).Tag)
  27. }
  28. }
  29. func tryMapWithType(students map[string]Student) {
  30. for key, theValue := range students {
  31. fmt.Printf("Key: %v, Value: %v, value pointer %p\n", key, theValue, &theValue)
  32. traversalTag(&theValue)
  33. }
  34. }
  35. func tryMapWithReflect(obj interface{}) {
  36. reflectMap := reflect.ValueOf(obj)
  37. for _, key := range reflectMap.MapKeys() {
  38. theValue := reflectMap.MapIndex(key).Interface()
  39. fmt.Printf("Key: %v, Value: %v, value pointer %p\n", key, theValue, &theValue)
  40. traversalTag(&theValue) // Will have error
  41. }
  42. }
  43. func main() {
  44. tryMapWithType(sam.Students)
  45. tryMapWithReflect(sam.Students)
  46. }

After run I got following error:

  1. Starting: C:\Users\Mento\go\bin\dlv.exe dap --check-go-version=false --listen=127.0.0.1:50308 from d:\Coding\Golang\demo
  2. DAP server listening at: 127.0.0.1:50308
  3. Key: Sen, Value: {Sen}, value pointer 0xc000044230
  4. Traversal tag with obj: type *main.Student, value &{Sen}
  5. Tag name Sname, value MyTag:"student-name"
  6. Key: Sen, Value: {Sen}, value pointer 0xc0000442c0
  7. Traversal tag with obj: type *interface {}, value 0xc0000442c0
  8. panic: reflect: NumField of non-struct type interface {}
  9. goroutine 1 [running]:
  10. reflect.(*rtype).NumField(0xec2d20)
  11. C:/Program Files/Go/src/reflect/type.go:1015 +0xc8
  12. main.traversalTag({0xebd9e0, 0xc0000442c0})
  13. d:/Coding/Golang/demo/demo.go:31 +0x1cb
  14. main.tryMapWithReflect({0xec3d40, 0xc00007a480})
  15. d:/Coding/Golang/demo/demo.go:48 +0x2c9
  16. main.main()
  17. d:/Coding/Golang/demo/demo.go:54 +0x38
  18. Process 8716 has exited with status 2
  19. dlv dap (11448) exited with code: 0

Can anyone hint how to get pointer of map members with original type information?

Thanks you,
Mento

答案1

得分: 1

如你所知,使用&theValue会解析为类型*interface{}。类型*interface{}与类型*Student是不同的,而你从tryMapWithType传递给traversalTag的是*Student类型。

如果你想从tryMapWithReflect*Student传递给traversalTag,你需要使用反射创建该指针值。普通的原生Go地址操作符&是不够的。

当你有一个可寻址的reflect.Value时,你只需要调用.Addr()方法就可以获得指向可寻址值的指针,然而映射元素是不可寻址的,因此reflectMap.MapIndex(key)是不可寻址的。所以,对你来说,不可能通过reflectMap.MapIndex(key).Addr().Interface()来获取*Student

因此,你唯一的选择是使用反射来创建*Student类型的新值,将指向的值设置为映射中的值,然后返回该值的.Interface()

另外,你可以从traversalTag中省略.Elem(),这样你就不必传递指针给它。

英文:

As you know using &theValue resolves to the type *interface{}. The type *interface{} is distinct from the type *Student which is what you are passing in to traversalTag from tryMapWithType.

If you want to pass *Student to traversalTag from tryMapWithReflect you need to create that pointer value using reflection. Plain native Go address operator & just isn't enough.

When you have a reflect.Value that is addressable all you need to do is to call the .Addr() method to get a pointer to the addressable value, however map elements are not addressable and therefore reflectMap.MapIndex(key) is not addressable. So, unfortunately for you, it's not possible to do reflectMap.MapIndex(key).Addr().Interface() to get *Student.

So your only option is to use reflection to create a new value of the *Student type, set the pointed-to value to the value in the map, and then return the .Interface() of that.

  1. func tryMapWithReflect(obj interface{}) {
  2. reflectMap := reflect.ValueOf(obj)
  3. for _, key := range reflectMap.MapKeys() {
  4. theValue := reflectMap.MapIndex(key).Interface()
  5. // allocate a new value of type *Student
  6. newValue := reflect.New(reflectMap.MapIndex(key).Type())
  7. // use Elem do dereference *Stunded
  8. // and then use Set to set the Student to the content of theValue
  9. newValue.Elem().Set(reflect.ValueOf(theValue))
  10. fmt.Printf("Key: %v, Value: %v, value pointer %p\n", key, newValue.Elem().Interface(), newValue.Interface())
  11. // return the newValue *Student
  12. traversalTag(newValue.Interface())
  13. }
  14. }

https://go.dev/play/p/pNL2wjsOW5y


Alternatively, just drop the .Elem() from the traversalTag and then you don't have to pass pointers to it.

  1. func traversalTag(obj interface{}) {
  2. theType := reflect.TypeOf(obj)
  3. fmt.Printf("Traversal tag with obj: type %v, value %v\n", theType.String(), obj)
  4. elem := reflect.TypeOf(obj)
  5. for i := 0; i < elem.NumField(); i++ {
  6. fmt.Printf("Tag name %s, value %s\n", elem.Field(i).Name, elem.Field(i).Tag)
  7. }
  8. }

https://go.dev/play/p/EwJ5e0uc2pd

huangapple
  • 本文由 发表于 2022年3月16日 21:22:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/71497849.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定