Go语言自定义JSON序列化实现:Marshaler接口与自定义接收器解析
许多 Go 开发者在调用 json.Marshal 时,会发现自定义的 MarshalJSON 方法没有被触发,或者序列化结果与预期相差甚远。根本原因在于:json.Marshal 只会调用那些直接实现了 json.Marshaler 或 json.Unmarshaler 接口的方法。如果方法采用指针接收器实现,但传入的是值类型,或者结构体中包含未导出或不支持类型的字段,那么序列化要么不生效,要么直接报错。

为什么 json.Marshal 不调用你的方法?
这是 Go 新手最容易踩到的陷阱之一。需要明白,Go 的 JSON 序列化仅识别两个接口——json.Marshaler 和 json.Unmarshaler,并且只有当类型直接实现这两个接口时才会生效。最常见的错误场景是:为指针类型实现了接口,但传给 json.Marshal 的却是值类型的变量,此时自定义方法完全不会被调用。
举个例子,定义了 func (t *MyType) MarshalJSON() ([]byte, error),但传入的是 MyType{}(值而非指针),那么 MarshalJSON 不会执行。
- 首先检查接收器类型:值接收器适用于值变量,指针接收器适用于指针变量或取地址后的值
- 接着确认变量实际类型:使用
fmt.Printf("%T", v)查看传入的是MyType还是*MyType - 当结构体字段为值类型时,嵌套嵌入也要确保接收器一致性,否则嵌套序列化会跳过自定义逻辑
如何让时间字段输出为 "2006-01-02" 而不是 RFC3339?
标准库 time.Time 的 MarshalJSON 默认生成带时区和纳秒的完整 RFC3339 格式字符串,但实际业务场景中通常只需要简洁的日期格式,例如 "2006-01-02"。由于标准库类型不可修改,不能直接改动 time.Time,因此需要包装一层自定义类型。
type Date time.Time
func (d Date) MarshalJSON() ([]byte, error) {
return []byte(`"` + time.Time(d).Format("2006-01-02") + `"`), nil
}
// 用法:struct 中字段类型声明为 Date,不是 time.Time
type Event struct {
When Date `json:"when"`
}
- 务必确保返回的字节包含双引号,因为
json.Marshal不会自动添加 - 不要遗漏
time.Time(d)类型转换,否则无法调用Format方法 - 若需要反序列化,则必须同时实现
UnmarshalJSON,并正确处理带引号的字符串输入
嵌套结构体里部分字段想忽略,但又不想用 json:"-"?
全局忽略使用 tag 是最便捷的方式,但有时逻辑更复杂,比如希望只在特定状态(如 Status == "draft")下才忽略某个字段。这种情况下 tag 无法满足需求,需要通过自定义 MarshalJSON 来手动控制字段的输出与否。
func (u User) MarshalJSON() ([]byte, error) {
type Alias User // 防止无限递归
raw := struct {
*Alias
CreatedAt string `json:"created_at,omitempty"`
}{
Alias: (*Alias)(&u),
}
if u.Status != "draft" {
raw.CreatedAt = u.CreatedAt.Format(time.RFC3339)
}
return json.Marshal(raw)
}
- 关键在于使用
type Alias User,以避免递归调用自身的MarshalJSON导致死循环 - 字段名的大小写必须与目标 JSON key 完全一致,否则对应字段不会出现在输出中
- 避免在
MarshalJSON中直接拼接 JSON 字符串,这种方法易出错且难以维护;推荐优先使用 map 或匿名 struct 路线
为什么实现了 MarshalJSON 却仍报 json: unsupported type?
您可能会疑惑:明明实现了接口,为什么仍然报错?实际上,这个错误通常不是因为接口未实现,而是因为底层字段类型不被 json.Marshal 所支持,例如字段为 func、chan、map[interface{}]interface{},或者是未导出的字段(小写字母开头)。
- 检查结构体中的所有字段是否可导出(首字母大写),否则即便实现了接口,内部字段也无法通过反射访问
- 确认没有嵌入不支持序列化的类型,例如
http.Handler、sync.Mutex等,它们本身不可序列化 - 若使用
map[string]interface{}存储动态数据,需确保值类型都在 JSON 支持范围内(string、int、float、bool、map、slice、nil) - 对于第三方类型(如
uuid.UUID),需自行实现MarshalJSON,或利用其自带序列化方法转换为字符串后再存储
最稳妥的方案是:在自定义 MarshalJSON 中显式列出需要序列化的字段,而不是依赖反射自动遍历。这既能避免 unsupported type 报错,又能精确控制输出内容。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
RecyclerView不显示内容的常见原因及修复
RecyclerView无数据显示,常见原因为Adapter的getItemCount()返回0。修复方法是将硬编码的0改为动态返回数据大小,如contacts size()。增强版Adapter需实现空安全及刷新支持。其他检查点包括设置布局管理器、避免RecyclerView高度为wrap_content、确保Item布局宽高合理及数据非空验证。
Python一行代码读取多种类型输入
使用`map(call,(int,str,int),input() split())`可一行代码解析混合类型输入,实现类型自动转换,比列表推导式更简洁。输入字段数量需与类型元组严格一致,支持封装为`read_types`函数复用。
Java中高效操作对象集合:避免无意义的Map构建
直接遍历对象集合并访问嵌套字段执行操作,时间复杂度O(n)且无额外内存开销。先构建Map再遍历则增加哈希表初始化、键值插入和二次迭代消耗,数据量大时性能差距显著,应避免此类功能冗余。
BoxLayout仅居中一个组件其余默认对齐的方法
在Swing的BoxLayout(Y_AXIS)中,setAlignmentX无法单独居中组件,因为该布局下所有组件的对齐由容器统一管理。三种可靠方案:嵌套JPanel通过分组隔离可分别设置左对齐和居中;GridBagLayout可独立控制每个组件的对齐方式;RelativeLayout允许组件单独设置其对齐方式。
Avro枚举兼容性:新增值失败原因与正确演进实践
Avro枚举向后兼容依赖二进制索引映射,JSON序列化因绕过索引机制导致新增符号失败;default仅对字段缺失生效,无法处理未知符号。演进需在末尾追加符号并采用二进制格式,推荐启用SchemaRegistry确保兼容。
- 日榜
- 周榜
- 月榜
1
2
3
4
5
6
7
8
9
10
相关攻略
2026-07-07 06:56
2026-07-07 06:55
2026-07-07 06:55
2026-07-07 06:55
2026-07-07 06:55
2026-07-07 06:55
2026-07-07 06:55
2026-07-07 06:55
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

