用go反射遍历任意结构体字段需传指针,通过reflect.ValueOf和reflect.typeof获取值与类型,递归处理结构体、切片、map等,支持标签解析与缩进打印。

用 Go 语言遍历任意结构体字段并打印,核心是 反射(reflect)。Go 不支持传统意义上的“运行时字段枚举”,但 reflect 包提供了安全、通用的方案,适用于任意嵌套结构体、指针、基础类型、切片、map 等。
获取结构体反射值并遍历字段
必须传入结构体指针(*T),否则 reflect.Value 无法获取导出字段(首字母大写)。使用 reflect.TypeOf 和 reflect.ValueOf 获取类型和值信息,再通过 .NumField() 和 .Field(i) 遍历:
- 先判断是否为指针,用
.Elem()解引用到实际结构体值 - 确保值是结构体(
.kind() == reflect.Struct) - 用
.Type().Field(i)获取字段名、标签;.Field(i)获取字段值
处理嵌套结构体与常见类型
递归是关键。对每个字段值做类型判断,再决定是否深入:
- 结构体或指针指向结构体:递归调用打印函数(注意空指针检查)
- 切片/数组:遍历元素,对每个元素递归处理
- map:遍历 key-value,value 递归处理
- 基本类型、字符串、布尔等:直接格式化输出
- nil 接口、nil 指针、未导出字段:跳过或打日志提示(不 panic)
支持结构体标签(tag)与缩进美化
通过 structField.Tag.Get("json") 或自定义 tag(如 "print")控制显示逻辑。配合层级深度参数实现缩进,提升可读性:
立即学习“go语言免费学习笔记(深入)”;
- 每层递归传入当前缩进级别(如
indent + " ") - 字段名优先使用 tag 中的
json名(若存在且非 “-“), fallback 到原始字段名 - 对 slice/map 长度、结构体地址等元信息可选择性打印(增强调试能力)
完整可用示例代码
以下是一个轻量、健壮的打印函数(已处理空指针、循环引用简化、非导出字段跳过):
func PrintStruct(v interface{}, indent string) { rv := reflect.ValueOf(v) if !rv.IsValid() { fmt.Println(indent + "(invalid)") return } if rv.Kind() == reflect.Ptr { if rv.IsNil() { fmt.Println(indent + "<nil>") return } fmt.Println(indent + "&{") PrintStruct(rv.Elem().Interface(), indent+" ") fmt.Println(indent + "}") return } if rv.Kind() != reflect.Struct { fmt.Printf("%s%vn", indent, rv.Interface()) return } rt := reflect.TypeOf(v).Elem() for i := 0; i < rv.NumField(); i++ { field := rt.Field(i) value := rv.Field(i) if !value.CanInterface() { // 非导出字段跳过 continue } name := field.Name if jsonTag := field.Tag.Get("json"); jsonTag != "" && jsonTag != "-" { if idx := strings.Index(jsonTag, ","); idx > 0 { name = jsonTag[:idx] } else { name = jsonTag } } fmt.Printf("%s%s: ", indent, name) PrintStruct(value.Interface(), indent+" ") } }