当前位置:首页 > 文章列表 > Golang > Go教程 > Go json.Marshal 怎么实现自定义枚举文本和值校验

Go json.Marshal 怎么实现自定义枚举文本和值校验

来源:17golang原创 2026-09-09 05:09:34 0浏览 收藏

Go 的自定义枚举通常底层是 int。直接调用 json.Marshal 时,客户端看到的往往是 12 这样的数字;而接口协议更希望看到 "paid""canceled"。可行的做法是给枚举实现 MarshalJSON() ([]byte, error):合法值先映射成文本,再调用 json.Marshal 编成 JSON 字符串;未定义值直接返回错误,不让脏数据悄悄出现在响应里。

要点速览
  • 命名整数类型默认按 JSON 数字输出,只有实现 json.Marshaler 才能改变表示方式。
  • 合法性判断应集中在 statusLabelMarshalJSON 只负责把判断结果交给 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)
    }
}

StatusStatusPendingStatusPaidStatusCanceledstatusLabel 共同组成状态表。以后增加一个枚举时,只需要同时补常量和这个出口;遗漏就会在编码阶段暴露,而不是等客户端猜一个数字含义。

Go Status 枚举、statusLabel 状态表与合法文本值之间的静态关系
图1:查看 Status 与 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、Status.MarshalJSON、statusLabel 与 ErrInvalidStatus 的静态调用关系
图2:图中展示 json.Marshal、Status.MarshalJSON、statusLabel 与 ErrInvalidStatus 的关系,定位文本输出和校验责任。

嵌入结构体后区分成功输出和失败边界

把枚举放进业务结构体,观察的是整个响应边界,而不只是单独调用方法。合法状态会自然嵌入字段;未知状态则让整次 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 调用方法的机会。枚举通常不需要修改自身,使用值接收者更直接。

未知值应该输出空字符串吗?

如果空字符串也是合法协议值,可以单独定义它;否则返回错误更安全。静默输出空字符串会把“程序漏填”伪装成“正常状态”。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Python free-threaded 构建中 C 扩展如何声明线程安全状态Python free-threaded 构建中 C 扩展如何声明线程安全状态
上一篇
Python free-threaded 构建中 C 扩展如何声明线程安全状态
Linux 进程收到 SIGPIPE 时怎么避免网络服务直接退出
下一篇
Linux 进程收到 SIGPIPE 时怎么避免网络服务直接退出
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    34次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    189次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    128次使用
  • AI Prompt Library:免费AI提示词库,助力ChatGPT高效创作与营销
    AI Prompt Library
    探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
    50次使用
  • Generrated:DALL·E 2/3 AI绘画提示词灵感库与图像对比平台
    Generrated
    Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
    36次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码