golang reflect 无法识别来自地图成员的标签

golang reflect cannot recognize tag from map members

我想用reflect提取struct的map成员的标签,但是我发现如果从MapIndex中检索成员的值,它的类型将被识别为“*interface{}”,因此所有类型信息都丢失了,没有提及反映可以提取详细信息。

package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Sname string `MyTag:"student-name"`
}

type Teacher struct {
    Name     string             `MyTag:"teacher-name"`
    Students map[string]Student `MyTag:"teacher-students"`
}

var sam = Teacher{
    Name: "Sam",
    Students: map[string]Student{
        "Sen": {
            Sname: "Sen",
        },
    },
}

func traversalTag(obj interface{}) {
    theType := reflect.TypeOf(obj)
    fmt.Printf("Traversal tag with obj: type %v, value %v\n", theType.String(), obj)

    elem := reflect.TypeOf(obj).Elem()
    for i := 0; i < elem.NumField(); i++ {
        fmt.Printf("Tag name %s, value %s\n", elem.Field(i).Name, elem.Field(i).Tag)
    }
}

func tryMapWithType(students map[string]Student) {
    for key, theValue := range students {
        fmt.Printf("Key: %v, Value: %v, value pointer %p\n", key, theValue, &theValue)
        traversalTag(&theValue)
    }
}

func tryMapWithReflect(obj interface{}) {
    reflectMap := reflect.ValueOf(obj)
    for _, key := range reflectMap.MapKeys() {
        theValue := reflectMap.MapIndex(key).Interface()
        fmt.Printf("Key: %v, Value: %v, value pointer %p\n", key, theValue, &theValue)
        traversalTag(&theValue) // Will have error
    }
}

func main() {
    tryMapWithType(sam.Students)
    tryMapWithReflect(sam.Students)
}

在 运行 之后出现以下错误:

Starting: C:\Users\Mento\go\bin\dlv.exe dap --check-go-version=false --listen=127.0.0.1:50308 from d:\Coding\Golang\demo
DAP server listening at: 127.0.0.1:50308
Key: Sen, Value: {Sen}, value pointer 0xc000044230
Traversal tag with obj: type *main.Student, value &{Sen}
Tag name Sname, value MyTag:"student-name"
Key: Sen, Value: {Sen}, value pointer 0xc0000442c0
Traversal tag with obj: type *interface {}, value 0xc0000442c0
panic: reflect: NumField of non-struct type interface {}

goroutine 1 [running]:
reflect.(*rtype).NumField(0xec2d20)
    C:/Program Files/Go/src/reflect/type.go:1015 +0xc8
main.traversalTag({0xebd9e0, 0xc0000442c0})
    d:/Coding/Golang/demo/demo.go:31 +0x1cb
main.tryMapWithReflect({0xec3d40, 0xc00007a480})
    d:/Coding/Golang/demo/demo.go:48 +0x2c9
main.main()
    d:/Coding/Golang/demo/demo.go:54 +0x38
Process 8716 has exited with status 2
dlv dap (11448) exited with code: 0

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

谢谢你, 门托

如您所知,使用 &theValue 解析为类型 *interface{}*interface{} 类型不同于 *Student 类型,后者是您从 tryMapWithType.

传递给 traversalTag 的类型

如果您想将 *StudenttryMapWithReflect 传递到 traversalTag,您需要使用反射创建该指针值。普通的原生 Go 地址运算符 & 是不够的。

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

所以你唯一的选择是使用反射创建一个新的*Student类型的值,将pointed-to值设置为映射中的值,然后return .Interface() 个。

func tryMapWithReflect(obj interface{}) {
    reflectMap := reflect.ValueOf(obj)
    for _, key := range reflectMap.MapKeys() {
        theValue := reflectMap.MapIndex(key).Interface()

        // allocate a new value of type *Student
        newValue := reflect.New(reflectMap.MapIndex(key).Type())

        // use Elem do dereference *Stunded
        // and then use Set to set the Student to the content of theValue
        newValue.Elem().Set(reflect.ValueOf(theValue))

        fmt.Printf("Key: %v, Value: %v, value pointer %p\n", key, newValue.Elem().Interface(), newValue.Interface())

        // return the newValue *Student
        traversalTag(newValue.Interface())
    }
}

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


或者,只需从 traversalTag 中删除 .Elem(),然后您就不必传递指向它的指针。

func traversalTag(obj interface{}) {
    theType := reflect.TypeOf(obj)
    fmt.Printf("Traversal tag with obj: type %v, value %v\n", theType.String(), obj)

    elem := reflect.TypeOf(obj)
    for i := 0; i < elem.NumField(); i++ {
        fmt.Printf("Tag name %s, value %s\n", elem.Field(i).Name, elem.Field(i).Tag)
    }
}

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