PrintFields函数使用reflect包遍历并打印结构体导出字段名与值:先解引用指针,验证为结构体后,循环NumField()获取字段名和Interface()值。

在 Go 中实现通用打印函数来遍历任意对象的字段值,核心是使用 reflect 包。Go 没有泛型反射(直到 Go 1.18+ 泛型配合反射仍需手动处理结构体),但通过 reflect.Value 和 reflect.Type 可安全、递归地访问导出字段(首字母大写)的名称与值。
只打印导出字段(推荐基础版)
Go 的反射无法读取未导出字段(小写开头),这是语言设计的安全限制。以下函数仅遍历并打印结构体中可访问的字段名和值:
- 用
reflect.ValueOf(v).Kind() == reflect.Ptr先解引用指针 - 确保传入的是结构体(
reflect.Struct),否则跳过或报错 - 遍历
NumField(),用Type.Field(i).Name获取字段名,Value.Field(i).Interface()获取值
示例代码:
func PrintFields(v interface{}) {
val := reflect.ValueOf(v)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
fmt.Printf("not a struct: %v\n", v)
return
}
typ := reflect.TypeOf(v)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
fmt.Println("Fields:")
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
value := val.Field(i).Interface()
fmt.Printf(" %s: %v\n", field.Name, value)
}
}
支持嵌套结构体与基础类型递归展开
若想深入打印嵌套结构体、map、slice 等复合类型,需递归处理。注意控制深度防止无限循环(如循环引用),并区分基础类型与容器类型:
立即学习“go语言免费学习笔记(深入)”;
- 对
struct:递归调用自身,加缩进标识层级 - 对
map:遍历 key-value,key 必须可比较(通常没问题),value 递归处理 - 对
slice/array:遍历每个元素,递归打印 - 对
interface{}:先取底层值再判断种类 - 跳过函数、channel、unsafe.Pointer 等不可打印类型
可封装为 PrintDeep(v interface{}, indent string),初始调用传 ""。
使用 JSON 或第三方库快速替代方案
如果目标只是“可读地查看字段”,不必手写反射:
-
fmt.Printf("%+v\n", obj):标准库最简方式,显示字段名和值(含未导出字段为零值,不显示真实值) -
json.MarshalIndent(obj, "", " "):自动忽略未导出字段,输出格式化 JSON(要求字段可序列化) - 用 go-spew:
spew.Dump(obj),深度打印所有字段(含未导出字段,调试利器)
注意事项与避坑点
反射易出错,实际使用需留意:
- 传入 nil 指针会 panic,调用前用
if v == nil或reflect.ValueOf(v).IsValid()校验 - 不能获取未导出字段真实值 —— 这是 Go 的封装原则,非 bug
- 反射性能较低,避免在热路径频繁调用;生产日志建议用结构化字段(如 zap.With...)代替通用打印
- 接口类型需先
val.Elem()再判断,否则Kind()可能是Interface而非目标类型











