如何在Golang中自定义JSON序列化行为 Go语言Marshaler接口与自定义接收器解析
很多开发者在使用 Go 的 json.Marshal 时,会发现自定义的 MarshalJSON 方法没有被调用,或者序列化结果和预期出入很大。这个问题的根源在于:json.Marshal 只在类型直接实现 json.Marshaler 或 json.Unmarshaler 接口的时候才会乖乖调用对
很多开发者在使用 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 是不可能的(标准库类型不可修改),所以得包装一层。
推荐的做法是:定义一个新类型,然后实现 json.Marshaler:
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 手动控制哪些字段输出、哪些不输出。
核心思路:手动构造一个 map,按条件决定是否加入键值对,然后再用 json.Marshal 编码这个 map:
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 报错,又能精确控制输出内容。
Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。
极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。
















