17370845950

如何在 Go 中遍历结构体时排除空字段

本文介绍如何使用反射(reflect)动态获取结构体中非空字段的名称,跳过零值(如空字符串、nil 指针、零整数等),适用于表单处理、api 请求过滤等场景。

在 Go 中,结构体字段默认初始化为对应类型的零值(如 string 为 "",int 为 0,指针为 nil)。当需要仅处理用户显式赋值的字段(例如解析 Web 表单或配置输入)时,直接遍历所有字段会包含大量无意义的零值字段。此时,需结合 reflect 包判断每个字段是否“非空”。

核心思路是:对每个字段的 reflect.Value 调用自定义的 empty() 判断函数,仅输出非空字段名。

以下是完整可运行的示例代码:

package main

import (
    "fmt"
    "reflect"
)

type Users struct {
    Name     string
    Password string
    Age      int
    Token    *string
}

// empty 判断 reflect.Value 是否为该类型的零值
func empty(v reflect.Value) bool {
    switch v.Kind() {
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        return v.Int() == 0
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        return v.Uint() == 0
    case reflect.String:
        return v.String() == ""
    case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Interface, reflect.Chan:
        return v.IsNil()
    case reflect.Bool:
        return !v.Bool()
    case reflect.Float32, reflect.Float64:
        return v.Float() == 0
    case reflect.Struct:
        // 可选:递归判断结构体是否全为零值(按需扩展)
        // 此处简化处理:视为非空(因 Struct 本身不可 nil)
        return false
    }
    return false
}

func main() {
    token := "abc123"
    u := Users{
        Name:     "Robert",
        Password: "",      // 空字符串 → 被排除
        Age:      0,       // 零值 int → 被排除
        Token:    &token,  // 非 nil 指针 → 保留
    }

    val := reflect.ValueOf(u)
    typ := reflect.TypeOf(u)

    fmt.Println("非空字段名:")
    for i := 0; i < val.NumField(); i++ {
        field := val.Field(i)
        if !empty(field) {
            fmt.Println(typ.Field(i).Name)
        }
    }
}

输出结果:

非空字段名:
Name
Token

关键说明:

  • empty() 函数覆盖了常见类型(字符串、数值、布尔、指针、切片、映射等)的零值判断逻辑;
  • 对 struct 类型未做深度判空(因其不可为 nil),若需严格检查嵌套结构体是否全为零值,可递归调用 empty();
  • 注意:reflect.ValueOf(u) 传入的是值拷贝,因此无法检测指针字段本身的 nil 状态以外的深层内容(如 *string 指向空字符串仍算“非空”,但其解引用值为空——这取决于业务语义);
  • 若结构体含导出字段(首字母大写)且需支持 JSON 标签映射等,可进一步结合 StructField.Tag 提取自定义标识(如 json:"name,omitempty")。

此方法轻量、通用,适合构建灵活的数据校验、序列化过滤或动态 API 参数提取模块。