
本文介绍如何在 go 项目中统一将结构体字段序列化为 snake_case 风格的 json 键名,避免在每个字段重复添加 json:"field_name" 标签,提供可复用的反射转换函数及使用注意事项。
本文介绍如何在 go 项目中统一将结构体字段序列化为 snake_case 风格的 json 键名,避免在每个字段重复添加 json:"field_name" 标签,提供可复用的反射转换函数及使用注意事项。
在 Go 的标准 encoding/json 包中,JSON 序列化默认遵循 Go 的导出规则:首字母大写的字段名(如 MyName)直接映射为同名 JSON 键("MyName"),而无法通过配置全局策略自动转为 snake_case(如 "my_name")。虽然可通过结构体标签(如 json:"my_name,omitempty")逐字段控制,但大规模微服务项目中易导致冗余、遗漏和维护成本上升。
所幸,Go 强大的反射能力允许我们构建一个无侵入、零标签依赖的通用转换层。以下是一个轻量、可嵌入的 SnakeCaseEncode 实现:
import (
"reflect"
"strings"
"unicode/utf8"
)
// SnakeCaseEncode 将任意结构体(或其指针)转换为 snake_case 键名的 map[string]interface{}
func SnakeCaseEncode(i interface{}) map[string]interface{} {
rt, rv := reflect.TypeOf(i), reflect.ValueOf(i)
// 解引用指针
if rt.Kind() == reflect.Ptr {
if rv.IsNil() {
return nil
}
rv = reflect.Indirect(rv)
rt = rv.Type()
}
// 仅支持结构体
if rt.Kind() != reflect.Struct {
return map[string]interface{}{"error": "input must be a struct or *struct"}
}
out := make(map[string]interface{}, rt.NumField())
for i := 0; i = 'A' && r 0 && (runes[i-1] 'Z') {
out = append(out, '_')
}
r += 'a' - 'A' // 转小写
}
out = append(out, r)
}
return string(out)
}
✅ 使用示例:
type User struct {
ID int `json:"-"` // 被忽略
FirstName string `json:"first_name,omitempty"` // 标签仍生效(兼容现有逻辑)
LastName string
IsActive bool
}
u := User{ID: 123, FirstName: "", LastName: "Doe", IsActive: true}
data := SnakeCaseEncode(u)
// 输出:map[string]interface{}{
// "first_name": "", // 因 omitempty + 空字符串 → 被跳过(注意:空字符串是零值)
// "last_name": "Doe",
// "is_active": true,
// }
⚠️ 重要注意事项:
- 该方案不替代 json.Marshal,而是作为预处理步骤;最终需配合 json.Marshal(SnakeCaseEncode(v)) 使用。
- 当前版本仅支持结构体(及其指针),如需递归处理嵌套结构体、切片、映射等复杂类型,需扩展递归逻辑(例如对 value.Kind() 进行分支判断并递归调用)。
- snakeCase 函数针对常见驼峰命名(MyName, HTTPStatus → my_name, http_status)做了优化,但不处理 Unicode 或特殊缩写(如 XMLID → x_m_l_i_d),生产环境建议引入成熟库如 mattes/go-sqlcipher 中的 ToSnakeCase 或 iancoleman/strcase。
- 性能敏感场景(高频 API)应权衡反射开销,可考虑代码生成工具(如 stringer 或自定义 go:generate)在编译期生成 MarshalJSON 方法。
? 总结:
虽然 Go 官方未提供全局 JSON 命名策略配置,但借助反射+约定式转换,我们能以极简代码实现组织级 snake_case 统一。它降低了结构体定义负担,提升了 API 一致性,是微服务中值得封装的基础工具之一。建议将其封装为独立包(如 pkg/jsonutil),并在 HTTP handler 层统一拦截响应体完成转换,真正实现“一次配置,处处生效”。











