Go json.Marshal 怎么实现自定义枚举文本和值校验
来源:17golang原创
时间:2026-09-09 05:09:34 367浏览 收藏
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 调用方法的机会。枚举通常不需要修改自身,使用值接收者更直接。
未知值应该输出空字符串吗?
如果空字符串也是合法协议值,可以单独定义它;否则返回错误更安全。静默输出空字符串会把“程序漏填”伪装成“正常状态”。
-
151 收藏
-
468 收藏
-
234 收藏
-
475 收藏
-
115 收藏
-
399 收藏
-
252 收藏
-
203 收藏
-
426 收藏
-
393 收藏
-
187 收藏
-
183 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习