在 Go 中利用反射reflect遍历结构体找出那些值是空字符串或数值零值0、0.0的字段并生成一个map[string]interface{}供后续使用比如过滤、日志或构造更新语句。下面我提供一个通用且健壮的解决方案并给出两种常见的输出形式只包含零值字段的 map键为字段名值为零值包含所有字段的 map同时额外标记哪些字段是零值或直接提供零值列表 核心函数GetZeroValueFields该函数接收一个结构体或指向结构体的指针返回一个 map其键为字段名值为对应的零值,0,nil等。gopackage main import ( fmt reflect ) // GetZeroValueFields 返回结构体中所有为零值的字段 // 支持结构体本身或指针会递归处理匿名字段但不递归处理嵌套结构体可根据需要扩展 func GetZeroValueFields(v interface{}) map[string]interface{} { result : make(map[string]interface{}) val : reflect.ValueOf(v) // 如果是指针解引用 if val.Kind() reflect.Ptr { val val.Elem() } // 必须是结构体 if val.Kind() ! reflect.Struct { return result } typ : val.Type() for i : 0; i val.NumField(); i { field : val.Field(i) fieldType : typ.Field(i) // 忽略未导出字段首字母小写 if fieldType.PkgPath ! { continue } // 判断字段是否为零值 if isZeroValue(field) { result[fieldType.Name] field.Interface() } } return result } // isZeroValue 判断 reflect.Value 是否为其类型的零值 func isZeroValue(v reflect.Value) bool { // 特殊情况无效值极少出现视为零 if !v.IsValid() { return true } switch v.Kind() { case reflect.String: return v.String() 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, reflect.Uintptr: return v.Uint() 0 case reflect.Float32, reflect.Float64: return v.Float() 0 case reflect.Complex64, reflect.Complex128: return v.Complex() 0 case reflect.Bool: return !v.Bool() case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func: return v.IsNil() case reflect.Array: // 数组检查所有元素是否为零 for i : 0; i v.Len(); i { if !isZeroValue(v.Index(i)) { return false } } return true case reflect.Struct: // 结构体检查每个字段是否都为零 for i : 0; i v.NumField(); i { if !isZeroValue(v.Field(i)) { return false } } return true default: // 其他类型如 unsafe.Pointer一般不会遇到 return false } } 使用示例gotype User struct { ID int Name string Age int Email string json:email,omitempty Active bool Nickname *string // 指针 } func main() { var nick u : User{ ID: 0, Name: , Age: 25, Email: , Active: false, Nickname: nick, // 指向空字符串的指针isZeroValue 会判定为 nil不指针本身不是 nil但指向的是空字符串 // 注意指针指向空字符串指针本身不为 nil所以不视为零值。要检查指向的内容需额外处理。 } zeroMap : GetZeroValueFields(u) fmt.Println(zeroMap) // 输出map[Active:false Email: ID:0 Name:] // 注意Nickname 指针不为 nil所以未包含Active 为 false 是零值包含了。 } 两种变体生成完整 map包含所有字段如果你的目标是生成一个所有字段的 map类似 JSON 序列化但希望排除零值可以参考gofunc StructToMapExcludeZero(v interface{}) map[string]interface{} { result : make(map[string]interface{}) val : reflect.ValueOf(v) if val.Kind() reflect.Ptr { val val.Elem() } if val.Kind() ! reflect.Struct { return result } typ : val.Type() for i : 0; i val.NumField(); i { field : val.Field(i) fieldType : typ.Field(i) if fieldType.PkgPath ! { continue } if !isZeroValue(field) { result[fieldType.Name] field.Interface() } } return result }调用后只包含非零字段常用于 PATCH 更新或 JSON 序列化时的omitempty逻辑。⚠️ 注意事项与最佳实践场景建议性能反射较慢不要在高频路径中使用可考虑缓存字段信息如用sync.Map缓存reflect.Type的字段列表。指针字段上面的isZeroValue认为nil指针是零值但指向空字符串的非nil指针不会被视为零值。如果需要将“指向空字符串的指针”也视为空可以额外判断if v.Kind() reflect.Ptr !v.IsNil() v.Elem().Kind() reflect.String v.Elem().String() { return true }。嵌套结构体当前isZeroValue会递归检查结构体所有字段是否均为零值才认为该结构体是零值。如果你希望将任何非零嵌套结构体都视为非零这个逻辑是合理的。忽略特定字段可通过 struct tag 标记忽略如ignore:true在遍历时跳过。类型别名例如type MyInt int反射仍视为Int类型可正常处理。 扩展同时处理指针指向空字符串若你需要将*string指向空字符串也视为“空”可在isZeroValue中添加go// 在 switch 之前增加 if v.Kind() reflect.Ptr !v.IsNil() { elem : v.Elem() // 如果是指向基本类型且元素为零值则认为该指针是“零” if elem.Kind() reflect.String elem.String() { return true } // 可以继续添加其他类型如 *int 指向 0 等 if elem.Kind() reflect.Int elem.Int() 0 { return true } }这样即便指针非nil只要指向的内容是零值也会被包含进结果。 完整代码包你可以将上述函数放在一个util包中方便复用。如果还有更复杂的需求比如根据json:omitemptytag 来决定是否排除可以进一步结合 tag 解析这里不再展开。希望这能直接解决你的问题。如果有其他边缘情况如时间类型time.Time的零值判断可以按需在isZeroValue中添加分支。