框架默认json.marshal不够用,因无法处理时间格式定制、字段脱敏、枚举类型转换或方法结果嵌入等需求;唯一可靠解法是实现json.marshaler/json.unmarshaler接口,通过别名类型规避递归,并确保接收者类型与调用方式匹配。

为什么框架默认序列化不够用
HTTP 框架(如 Gin、Echo、Fiber)默认调用 json.Marshal 处理响应体,但实际业务中常遇到:时间格式要 "2006-01-02" 而非 RFC3339;密码字段需脱敏为 "***";枚举字段是字符串("active")却要反序列化到 int 类型;或结构体里嵌了 func() int 方法想暴露为 JSON 字段。这些都不是加个 json:"xxx" tag 能解决的——框架不接管你类型内部的编解码逻辑,只管“传进去,json.Marshal 一下”。
必须实现 json.Marshaler 和 json.Unmarshaler
这是唯一能绕过框架默认行为、让自定义逻辑生效的路径。框架在序列化前会检查类型是否实现了这两个接口,有就优先调用。
-
MarshalJSON()必须返回[]byte和error,不能直接调用json.Marshal(&s)(否则会递归触发自身);标准解法是定义别名类型(如type Alias MyStruct),再嵌套进匿名结构体 -
UnmarshalJSON([]byte)里不能直接json.Unmarshal(data, &s),否则同样递归;应先解析为map[string]json.RawMessage或临时 struct,再手动赋值 - 接收者用指针还是值类型,取决于你要修改原字段:如果反序列化要改
Created字段,就得用func (s *MyStruct) UnmarshalJSON(...) - 字段标签(如
json:"-")在别名类型里依然有效,可用来屏蔽原始字段,避免重复序列化
框架里怎么确保你的 MarshalJSON 被调用
常见失效场景不是代码写错,而是类型没被框架“看到”:
- 返回的是指针
*MyStruct,但MarshalJSON实现的是值接收者func (s MyStruct) MarshalJSON()—— 指针不实现该方法 - 结构体字段里嵌了自定义类型,但外层没用
json:",inline"或显式字段名,导致内层方法根本没机会执行 - 用了中间层包装(如
Response{Data: user}),而Response没实现Marshaler,框架只序列化它,你的user就被json.Marshal直接处理了 - Gin 的
c.JSON(200, user)会调,但c.Data(200, "application/json", b)不会——后者你已经自己序列化好了,框架不介入
UnmarshalJSON 容易 panic 的三个硬伤
框架把请求 body 交给你的 UnmarshalJSON 时,输入完全不可信。以下写法在线上必崩:
- 没检查
len(data)就直接读data[0]或copy(dst, data[4:])—— 触发panic: runtime error: index out of range - 把
uint8当作字符串长度,却没验证int(nameLen) > len(data)-headerLen,导致copy越界 - 用
binary.Read解析整数时 buffer 不足 —— 它不返回 error,而是直接 panic
安全做法:开头强制校验最小长度;读长度字段后立刻检查剩余字节是否足够;对变长整数优先用 binary.Uvarint,它自带边界保护。
真正麻烦的不是写逻辑,而是每个字段都要做防御性长度校验——协议越紧凑,越容易在反序列化这一步挂掉。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











