Go中无enum关键字,需用type+const模拟:定义底层类型(int/string)并用const声明具名值,配合iota自动递增;应实现String()及json.Marshaler/Unmarshaler接口以支持JSON序列化与反序列化。

Go 里没有 enum 关键字,但可以用 type + const 模拟
Go 语言本身不提供类似 Java 或 C# 的 enum 语法,但通过自定义类型配合 const 块是最常用、最清晰的替代方案。核心思路是:定义一个底层类型(如 int 或 string),再用 const 声明具名值,并让这些值属于该类型。
常见错误是直接用 int 或 string 做变量类型,导致类型检查失效——比如把任意整数赋给“状态码”变量,编译器无法拦截。
- 推荐写法:
type Status int <p>const ( StatusPending Status = iota StatusRunning StatusDone StatusFailed )</p>
- 用
iota自动递增,语义清晰且不易出错 - 如果需要 JSON 序列化为字符串,改用
string底层类型,并为每个 const 显式赋值:StatusPending = "pending" - 记得为类型实现
String()方法,方便日志和调试
如何让枚举支持 json.Marshal 和 json.Unmarshal
默认情况下,int-底层数值型枚举会以数字形式序列化,而 string-底层的则输出字符串。但若想混合使用(比如传数字但期望字符串格式),就得手动实现 json.Marshaler 和 json.Unmarshaler 接口。
典型坑点:只实现了 MarshalJSON 却忘了 UnmarshalJSON,导致反序列化失败并静默转为零值。
- 数值型枚举想输出字符串,需在
MarshalJSON中返回[]byte(fmt.Sprintf("%q", e.String())) -
UnmarshalJSON必须能处理字符串(如"pending")和数字(如0)两种输入,否则 API 兼容性差 - 别在
UnmarshalJSON里用strconv.Atoi粗暴转数字——要先判断输入是字符串还是数字,可用json.Unmarshal到interface{}再分支处理
用 map 做运行时校验不如编译期约束可靠
有人习惯定义一个 map[Status]string 来做名称映射或合法性检查,但这只是运行时防护,无法阻止非法值被构造出来(比如 Status(999))。Go 的强项是编译期类型安全,应优先利用它。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
真正需要运行时校验的场景其实很少,多见于解析外部不可信输入(如 HTTP query 参数)。
- 对外部输入做校验,建议封装成函数:
func ParseStatus(s string) (Status, error),内部用switch枚举合法字符串 - 避免全局
map查表——查找慢、内存占用高、还容易漏掉新增枚举项 - 如果真要用
map,务必用var statusNames = map[Status]string{...}并在init()函数中补全所有 const 项,防止遗漏
第三方库 go-enum 能生成代码,但引入依赖要慎重
go-enum 这类工具通过注释标记 + go:generate 自动生成 String()、Values()、MarshalJSON 等方法,省去手写样板代码。但它本质是代码生成,不是语言特性。
问题在于:生成的代码耦合构建流程,CI 环境若没跑 go generate 就可能编译失败;而且一旦升级库版本,生成逻辑变化,行为可能意外变更。
- 适合中大型项目且枚举频繁变更的场景;小项目或稳定枚举,手写更可控
- 生成的
String()方法通常用switch,比map查表快,也比反射安全 - 注意检查生成代码是否包含
UnmarshalJSON,有些旧版本默认不生成,导致反序列化失败
实际项目里,绝大多数情况用第一种 type + const + iota 配合手写 String() 就够了。复杂点在于一致性——比如所有枚举都要支持 JSON、都要有文档注释、都要进测试覆盖率,这些靠规范和 Code Review 比靠工具更可靠。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










