Golang反射问题 - 为何int64指针未被解引用而日期指针却被解引用

Golang反射问题 - 为何int64指针未被解引用而日期指针却被解引用 标题可能没有很好地说明问题。

我正在尝试创建一个通用函数,该函数可以接受任何类型的结构体,遍历其字段并显示其值。

问题在于,对于像这样的结构体:

type Some struct {
	Name  string
	Birth *strfmt.DateTime
	Value *int64
}

我的函数能正确返回 Name 和 Birth 的值,但对于 Value 则不行。

函数如下:

func someThing(data interface{}) {
	ref := reflect.ValueOf(data)

	for i := 0; i < ref.NumField(); i++ {
		prop := ref.Type().Field(i).Name
		val := fmt.Sprintf("%v", ref.Field(i).Interface())
		valType := fmt.Sprintf("%T", ref.Field(i).Interface())
		rawValue := ref.Field(i).Interface()
		fmt.Println(prop, val, valType, rawValue)
	}
}

例如,这会返回:

Name me string me Birth 2009-11-10T23:00:00.000Z *strfmt.DateTime 2009-11-10T23:00:00.000Z <---- 这里是值 Value 0xa80f68 *int64 0xa80f68 <---- 这里是地址

完整代码在 playground 上:https://play.golang.org/p/RAQ8HLOCUB2

(来自 github 的外部导入可能不稳定,尝试点击格式化并再次运行)

注意:此函数的最终目标是将键和值追加到切片中并返回它们

	var props []string
	var vals []interface{}

更多关于Golang反射问题 - 为何int64指针未被解引用而日期指针却被解引用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

Some.ValueSome.Birth 字段的接口方法返回类型是指针时,必须使用 reflect.IndirectElem 来获取指针指向的值。

reflect.Indirect 函数会返回零个或多个指针所指向的值。 reflect.Value.Elem 方法会返回一次指针指向的值,如果 ValueKind 不是 InterfacePtr,则会引发 panic。

		rawValue := reflect.Indirect(ref.Field(i)).Interface()

更多关于Golang反射问题 - 为何int64指针未被解引用而日期指针却被解引用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这是一个典型的反射处理指针类型不一致的问题。问题出在 fmt.Sprintf("%v", ...) 对不同类型的指针处理方式不同。

strfmt.DateTime 类型实现了 fmt.Stringer 接口,所以即使是指针类型,%v 格式也会调用其 String() 方法。而 int64 指针没有实现 Stringer 接口,所以 %v 会显示指针地址。

以下是修复后的代码:

func someThing(data interface{}) {
    ref := reflect.ValueOf(data)
    
    for i := 0; i < ref.NumField(); i++ {
        field := ref.Field(i)
        prop := ref.Type().Field(i).Name
        
        // 获取字段的接口值
        var val interface{}
        if field.Kind() == reflect.Ptr && !field.IsNil() {
            // 如果是非空指针,解引用获取实际值
            val = field.Elem().Interface()
        } else {
            val = field.Interface()
        }
        
        // 格式化显示
        valStr := fmt.Sprintf("%v", val)
        valType := fmt.Sprintf("%T", val)
        
        fmt.Println(prop, valStr, valType, val)
    }
}

更完整的解决方案,处理所有指针类型:

func someThing(data interface{}) {
    ref := reflect.ValueOf(data)
    
    for i := 0; i < ref.NumField(); i++ {
        field := ref.Field(i)
        prop := ref.Type().Field(i).Name
        
        // 递归解引用直到获取非指针值
        val := field
        for val.Kind() == reflect.Ptr && !val.IsNil() {
            val = val.Elem()
        }
        
        var displayVal interface{}
        if val.IsValid() {
            displayVal = val.Interface()
        } else {
            displayVal = nil
        }
        
        valStr := fmt.Sprintf("%v", displayVal)
        valType := fmt.Sprintf("%T", displayVal)
        
        fmt.Println(prop, valStr, valType, displayVal)
    }
}

对于你的具体需求(获取键值对切片),可以这样实现:

func extractFieldValues(data interface{}) ([]string, []interface{}) {
    ref := reflect.ValueOf(data)
    var props []string
    var vals []interface{}
    
    for i := 0; i < ref.NumField(); i++ {
        field := ref.Field(i)
        prop := ref.Type().Field(i).Name
        
        // 处理指针字段
        var val interface{}
        if field.Kind() == reflect.Ptr {
            if field.IsNil() {
                val = nil
            } else {
                val = field.Elem().Interface()
            }
        } else {
            val = field.Interface()
        }
        
        props = append(props, prop)
        vals = append(vals, val)
    }
    
    return props, vals
}

使用示例:

type Some struct {
    Name  string
    Birth *strfmt.DateTime
    Value *int64
}

func main() {
    birthTime := strfmt.DateTime(time.Now())
    value := int64(42)
    
    data := Some{
        Name:  "me",
        Birth: &birthTime,
        Value: &value,
    }
    
    props, vals := extractFieldValues(data)
    for i := 0; i < len(props); i++ {
        fmt.Printf("%s: %v (type: %T)\n", props[i], vals[i], vals[i])
    }
}

关键点:使用反射时,需要显式检查字段是否为指针类型,并通过 Elem() 方法解引用。fmt 包对实现了 Stringer 接口的指针类型会自动调用其 String() 方法,但对于普通指针类型则显示地址。

回到顶部