Go json.Marshal 怎么实现自定义枚举文本和值校验
Go 的自定义枚举通常底层是 int。直接调用 json.Marshal 时,客户端看到的往往是 1、2 这样的数字;而接口协议更希望看到 "paid"、"canceled"。可行的做法是给枚举实现 MarshalJSON() ([]byte, error):合法值先映射成文本,再调用 json.Marshal 编成 JSON 字符串;未定义值直接返回错误,不让脏数据悄悄出现在响应里。
- 命名整数类型默认按 JSON 数字输出,只有实现
json.Marshaler才能改变表示方式。 - 合法性判断应集中在
statusLabel,MarshalJSON只负责把判断结果交给 JSON 编码器。 - 非法枚举返回自定义错误,外层可用
errors.Is判断,json.Marshal会将它包装成json.MarshalerError。
先把枚举的合法集合写成唯一出口
先定义状态和值域。这里故意保留零值 StatusUnknown,因为 Go 结构体的零值很容易进入业务对象;是否允许它出现在接口中,要由映射函数明确决定。映射表只表达“哪个值对应哪个文本”,不在多个方法里重复写 switch。
package order
import (
"errors"
"fmt"
)
type Status uint8
const (
StatusUnknown Status = iota // 零值单独保留,避免误当成已支付
StatusPending // 等待支付
StatusPaid // 已支付
StatusCanceled // 已取消
)
var ErrInvalidStatus = errors.New("invalid order status")
func statusLabel(s Status) (string, error) {
switch s {
case StatusPending:
return "pending", nil
case StatusPaid:
return "paid", nil
case StatusCanceled:
return "canceled", nil
default:
// 未定义值不输出数字,直接阻止它进入接口响应。
return "", fmt.Errorf("%w: %d", ErrInvalidStatus, s)
}
}
Status、StatusPending、StatusPaid、StatusCanceled 和 statusLabel 共同组成状态表。以后增加一个枚举时,只需要同时补常量和这个出口;遗漏就会在编码阶段暴露,而不是等客户端猜一个数字含义。

MarshalJSON 里先校验,再编码文本
实现方法时使用值接收者,普通的 Status 值和结构体字段都能直接参与编码。不要手写带引号的字符串,因为枚举文本一旦包含引号、换行或其他特殊字符,手写结果就可能不是合法 JSON;让 json.Marshal 负责最后一步更稳妥。
func (s Status) MarshalJSON() ([]byte, error) {
label, err := statusLabel(s)
if err != nil {
// 保留 ErrInvalidStatus,调用方可以用 errors.Is 识别原因。
return nil, err
}
// 由 encoding/json 负责字符串转义和合法 JSON 格式。
return json.Marshal(label)
}
上面的代码还需要在 import 中加入 encoding/json。关键顺序是“先判定、后编码”:Status(99) 不会被当成普通整数输出,合法的 StatusPaid 则得到 "paid"。官方 encoding/json Marshaler 文档规定了这个方法契约,返回错误时不应继续拼装部分 JSON。

嵌入结构体后区分成功输出和失败边界
把枚举放进业务结构体,观察的是整个响应边界,而不只是单独调用方法。合法状态会自然嵌入字段;未知状态则让整次 json.Marshal 失败,这通常比返回一个无法解释的数字更容易监控和回滚。
type Order struct {
ID string `json:"id"` // 对外稳定的订单编号
Status Status `json:"status"` // 使用 Status 的自定义 JSON 表示
}
func encodeOrder(order Order) ([]byte, error) {
data, err := json.Marshal(order)
if err != nil {
// 外层只把原始错误作为根因,避免吞掉非法枚举信息。
if errors.Is(err, ErrInvalidStatus) {
return nil, fmt.Errorf("encode order status: %w", err)
}
return nil, err
}
return data, nil
}
Order{ID: "A-100", Status: StatusPaid} 的结果是 {"id":"A-100","status":"paid"}。当状态为 Status(99) 时,底层错误会被 json.MarshalerError 包装,但 errors.Is(err, ErrInvalidStatus) 仍可识别根因。日志里应记录订单编号和状态数值,接口层则返回统一的内部编码失败响应。
| 值 | JSON 表示 | 处理方式 |
|---|---|---|
| StatusPending | "pending" | 正常输出 |
| StatusPaid | "paid" | 正常输出 |
| StatusUnknown / Status(99) | 无输出 | 返回 ErrInvalidStatus |
需要接收文本时再补上 UnmarshalJSON
如果服务还要接收 "paid",可以为同一个 Status 增加 UnmarshalJSON,解析文本后复用相同的合法集合。不要只实现一半却在写入数据库前假设输入一定合法;反序列化是另一条边界,未知文本也应返回错误。
func (s *Status) UnmarshalJSON(data []byte) error {
var label string
if err := json.Unmarshal(data, &label); err != nil {
// 非字符串输入直接失败,避免把 JSON 数字误当成文本状态。
return err
}
labels := map[string]Status{
"pending": StatusPending,
"paid": StatusPaid,
"canceled": StatusCanceled,
}
value, ok := labels[label]
if !ok {
return fmt.Errorf("%w: %q", ErrInvalidStatus, label)
}
// 只有完整匹配后才改变目标值,避免失败时留下半更新状态。
*s = value
return nil
}
若只需要对外输出文本,不必为了“成对”而增加反序列化代码。真正需要接收 JSON 文本时,再决定大小写、空字符串、旧别名和兼容期;这些都是协议决策,不能由枚举常量的数字值自动推导。
常见问题
为什么不直接给 Status 实现 String 方法?
String 只影响显式格式化,encoding/json 不会因为它存在就自动采用文本结果。要改变 JSON 表示,应实现 MarshalJSON。
MarshalJSON 用指针接收者可以吗?
可以,但值是否可寻址会影响旧版 encoding/json 调用方法的机会。枚举通常不需要修改自身,使用值接收者更直接。
未知值应该输出空字符串吗?
如果空字符串也是合法协议值,可以单独定义它;否则返回错误更安全。静默输出空字符串会把“程序漏填”伪装成“正常状态”。
Python free-threaded 构建中 C 扩展如何声明线程安全状态
- 上一篇
- Python free-threaded 构建中 C 扩展如何声明线程安全状态
- 下一篇
- Linux 进程收到 SIGPIPE 时怎么避免网络服务直接退出
-
- Golang · Go教程 | 27分钟前 |
- Go encoding/gob 跨进程传接口值为什么需要注册类型
- 151浏览 收藏
-
- Golang · Go教程 | 52分钟前 | go · sse · 流式响应 · http.Flusher ·
- Go http.Flusher 什么时候能把流式响应及时发给客户端
- 468浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go net/http 如何读取 Trailer 头而不是普通 Header
- 234浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go archive/tar 归档符号链接时怎么控制跟随行为
- 475浏览 收藏
-
- Golang · Go教程 | 1小时前 | go · 图片处理 · image/draw · Go image/draw Go图片裁切 Go缩略图
- Go image/draw 怎么把缩略图裁成固定比例
- 115浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go image.Decode 怎么根据文件头识别图片格式
- 399浏览 收藏
-
- Golang · Go教程 | 1小时前 | go · 错误排查 · 文件系统 · 嵌入资源 · Go embed.FS io/fs fs.ReadFile ReadFileFS
- Go io/fs.ReadFileFS 读取嵌入资源失败怎么排查
- 252浏览 收藏
-
- Golang · Go教程 | 2小时前 | GO文件 · 文件系统 · 数据可靠性 · Go 文件持久化 os.File.Sync 落盘
- Go os.File.Sync 适合用在什么持久化场景
- 203浏览 收藏
-
- Golang · Go教程 | 2小时前 | go · 日志采集 · 进程调用 · os/exec StdoutPipe StderrPipe
- Go os/exec 怎么把标准错误和标准输出分开保存
- 426浏览 收藏
-
- Golang · Go教程 | 2小时前 | go · 资源管理 · runtime · runtime.KeepAlive Go终结器 Go句柄生命周期
- Go runtime.KeepAlive 为什么能保护底层句柄生命周期
- 393浏览 收藏
-
- Golang · Go教程 | 2小时前 | go · GC · 内存管理 · go垃圾回收 runtime.GC Go内存释放
- Go runtime.GC 手动触发后为什么不能当成内存释放按钮
- 187浏览 收藏
-
- Golang · Go教程 | 2小时前 |
- Go panic 发生后怎么用 defer 记录调用上下文
- 183浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 34次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 189次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 128次使用
-
- AI Prompt Library
- 探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
- 50次使用
-
- Generrated
- Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
- 36次使用
-
- 接口返回 200 但前端仍报错怎么办:从响应格式到跨域一步步排查
- 2026-06-14 332浏览
-
- Go map 并发写 panic 怎么办:从共享 map 到可控写入路径
- 2026-06-30 123浏览
-
- golang生成JSON以及解析JSON
- 2023-01-17 329浏览
-
- Go如何实现json字符串与各类struct相互转换
- 2023-01-07 377浏览
-
- Go中使用gjson来操作JSON数据的实现
- 2023-01-07 141浏览

