必须实现marshaljson方法当结构体字段不能被json.marshal正确表达时,如需脱敏、定制时间格式、扁平化嵌套、处理不支持类型等;须用指针接收者,返回合法json,避免循环调用。

什么时候必须实现 MarshalJSON 方法
当结构体字段不能直接被 json.Marshal 正确表达时,就必须自定义序列化逻辑。典型场景包括:字段需要脱敏(如密码字段输出为 "***")、时间格式要固定为 "2006-01-02" 而非默认的 RFC3339、嵌套结构需扁平化、或字段类型本身不支持 JSON 编码(比如 func、map[interface{}]interface{}、未导出字段参与编码等)。
注意:json.Marshal 只会编码导出字段(首字母大写),且默认忽略零值字段(除非加 omitempty 标签)。如果想控制零值行为、添加额外字段、或完全替换编码结果,绕不开 MarshalJSON。
MarshalJSON 方法签名和返回值陷阱
方法必须严格满足签名:func (t T) MarshalJSON() ([]byte, error),接收者类型必须是值类型或指针类型,但**不能混用**——如果你在指针接收者上实现了它,那 json.Marshal(t)(传值)不会调用该方法,因为底层反射找不到匹配的导出方法。
- 始终优先用指针接收者:
func (t *T) MarshalJSON(),避免因传值导致方法未被调用 - 返回的
[]byte必须是合法 JSON 片段,不能只写{"name":"x"}—— 缺少外层引号或括号会导致invalid character错误 - 不要在方法里直接调用
json.Marshal(t)再修改字节切片,容易破坏 JSON 结构;推荐先构造中间 map 或 struct,再 marshal
常见错误:循环引用与嵌套调用失控
在 MarshalJSON 里不小心对同一对象再次调用 json.Marshal(比如 json.Marshal(*t)),会重新触发 MarshalJSON,造成无限递归,最终 panic:runtime: goroutine stack exceeds 1000000000-byte limit。
正确做法是“绕过”自定义逻辑,用匿名结构体或强制转为基础类型:
func (u *User) MarshalJSON() ([]byte, error) {
type Alias User // 创建别名类型,不继承方法
return json.Marshal(&struct {
*Alias
Password string `json:"password"`
}{
Alias: (*Alias)(u),
Password: "***",
})
}
这个技巧依赖 Go 的类型别名机制:新类型 Alias 和原类型 User 底层相同,但方法集为空,因此 json.Marshal 不会再调用 MarshalJSON。
性能与兼容性提醒
每次调用 MarshalJSON 都会分配新字节切片,如果高频序列化,建议复用 bytes.Buffer 或提前预估长度;另外,该方法**不会影响 UnmarshalJSON** —— 反序列化仍走默认逻辑,除非你也实现它。两者必须成对考虑,否则容易出现序列化/反序列化不对称(比如序列化时隐藏了字段,反序列化却试图填充它,导致零值残留)。
还有一个易忽略点:如果结构体嵌套了其他也实现了 MarshalJSON 的类型,它们的方法仍会被正常调用;但若你手动拼接 JSON 字符串(比如用 fmt.Sprintf),就彻底绕过了所有自定义逻辑,后续维护成本陡增。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











