Go中无原生enum,需用int+iota模拟并实现json.Marshaler/Unmarshaler接口:MarshalJSON需校验值防panic,UnmarshalJSON应用strings.TrimSpace和ToLower处理大小写与空格,多枚举可考虑go-enum生成工具。

Go 中没有原生 enum,但可以用 iota 模拟
Go 语言本身不提供 enum 关键字,所以常见做法是用 int 类型配合 iota 定义常量组。但直接序列化这种类型时,默认会输出数字值,而非可读的字符串——这在 API 交互或日志中容易造成歧义。
比如定义状态:
type Status int const ( Pending Status = iota Running Finished )
如果直接用 json.Marshal 序列化 Status(Running),结果是 1,不是 "Running"。
- 必须为该类型实现
json.Marshaler和json.Unmarshaler接口 - 推荐把字符串映射逻辑封装在方法里,避免硬编码散落在各处
- 注意
iota从 0 开始,若业务要求从 1 起始,需手动调整起始值(如Pending Status = 1)
实现 MarshalJSON 时要检查无效值
用户可能传入未定义的整数值(如 Status(999)),若不校验就直接转字符串,会导致序列化结果为 "unknown" 或 panic。稳妥做法是在 MarshalJSON 中做边界判断。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
func (s Status) MarshalJSON() ([]byte, error) {
switch s {
case Pending:
return []byte(`"pending"`), nil
case Running:
return []byte(`"running"`), nil
case Finished:
return []byte(`"finished"`), nil
default:
return nil, fmt.Errorf("invalid status value: %d", s)
}
}
- 返回
error比返回"unknown"更利于快速定位问题 - 不要用
fmt.Sprintf拼接 JSON 字符串,避免引号/转义错误;用[]byte字面量更安全 - 如果允许未知值透传,至少加日志告警,而不是静默处理
反序列化时 string → int 映射容易忽略大小写和空格
HTTP 请求体里的字符串可能是任意大小写(如 "RUNNING"、"running "),而 Go 的 json.Unmarshal 默认严格匹配。不处理这些情况,会导致解析失败。
建议在 UnmarshalJSON 中统一做 trim + toLower:
func (s *Status) UnmarshalJSON(data []byte) error {
var v string
if err := json.Unmarshal(data, &v); err != nil {
return err
}
switch strings.ToLower(strings.TrimSpace(v)) {
case "pending":
*s = Pending
case "running":
*s = Running
case "finished":
*s = Finished
default:
return fmt.Errorf("unknown status: %s", v)
}
return nil
}
- 必须用指针接收者
*Status,否则赋值无效 - 先解到临时
string变量,再 switch,避免直接对data做字符串操作引发越界 - 别漏掉
strings.TrimSpace,前端表单提交常带空格
第三方库如 go-enum 能省事但有依赖成本
如果项目中枚举较多,手写每个类型的序列化逻辑重复度高。可以考虑 go-enum 这类代码生成工具,它通过注释标记自动生成 MarshalJSON/UnmarshalJSON 方法。
用法示例:
//go:enum //+enum:values=Pending,Running,Finished type Status int
- 生成代码可读性尚可,但调试时需跳转到生成文件
- 升级 Go 版本或重构字段时,容易忘记重新生成,导致序列化逻辑过期
- 如果只有一两个枚举,手写更轻量;超过五个,才值得引入生成方案
真正麻烦的不是怎么写序列化,而是确保前后端约定一致:比如后端接受 "pending",前端却发 "PENDING",这类问题往往卡在联调阶段才暴露。

















