如何让自定义的 MarshalJSON 方法被正确调用

小枫小哥_5608

小枫小哥_5608

2026-08-01

456人浏览

原创

如何让自定义的 MarshalJSON 方法被正确调用

当结构体实现 marshaljson() 方法时,若使用指针接收器但实际编码的是值类型(如 map[string]struct),该方法将不会被 json 包调用——根本原因是 go 的方法集规则:值类型不包含指针接收器方法。解决方式包括改为值接收器,或确保被编码的对象是对应指针类型。

当结构体实现 marshaljson() 方法时,若使用指针接收器但实际编码的是值类型(如 map[string]struct),该方法将不会被 json 包调用——根本原因是 go 的方法集规则:值类型不包含指针接收器方法。解决方式包括改为值接收器,或确保被编码的对象是对应指针类型。

在 Go 中,json.Marshal 和 json.Encoder.Encode 会自动查找并调用实现了 json.Marshaler 接口的类型方法:

type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

但能否成功调用,取决于方法接收器类型与实际值类型的匹配关系。

? 问题根源:方法集不匹配

你定义了:

func (u *Edition) MarshalJSON() ([]byte, error) { ... }

这是一个指针接收器方法,它只属于 *Edition 类型的方法集,而不属于 Edition 值类型的方法集。

而你的数据结构是:

var EditionsMap map[string]datamodel.Edition // ← 值类型,非指针

当 json 包遍历 EditionsMap 的 value(即 datamodel.Edition 实例)时,发现 Edition 类型本身没有 MarshalJSON 方法(因为方法集里只有 *Edition 有),于是退而使用默认结构体反射规则,跳过你的自定义逻辑——因此 "here" 永远不会打印。

✅ 解决方案一:改用值接收器(推荐)

只需将接收器从 *Edition 改为 Edition:

jm-jsjkxyjs02-pzl-803
jm-jsjkxyjs02-pzl-803

查询全球任意城市的实时天气和未来天气预报

下载
func (u Edition) MarshalJSON() ([]byte, error) {
    vaultValue := u.Vault != nil
    onlineValue := u.Online != nil

    // 使用类型别名避免无限递归
    type Alias Edition
    return json.Marshal(&struct {
        Vault  bool `json:"vault,omitempty"`
        Online bool `json:"online,omitempty"`
        *Alias
    }{
        Vault:  vaultValue,
        Online: onlineValue,
        Alias:  (*Alias)(&u),
    })
}

✅ 优势:

  • 无论 Edition 是值还是指针(Edition 或 *Edition)都能触发该方法;
  • 无需修改数据结构或调用方代码;
  • 符合多数场景的灵活性需求。

⚠️ 注意:(*Alias)(&u) 是关键——它将 &u 转为别名类型的指针,避免在嵌入 *Alias 时再次触发 MarshalJSON,防止无限递归。

✅ 解决方案二:统一使用指针类型

若坚持使用指针接收器,需确保 map 存储的是指针:

var EditionsMap map[string]*datamodel.Edition // ← 改为 *Edition

// 初始化示例:
EditionsMap = make(map[string]*datamodel.Edition)
EditionsMap["v1"] = &datamodel.Edition{Vault: new(string), Online: nil}

然后编码保持不变:

if err := json.NewEncoder(w).Encode(EditionsMap); err != nil {
    log.Printf("JSON encode error: %v", err) // 切勿忽略错误!
}

✅ 优势:适合需要区分零值/nil 的复杂场景;
❌ 劣势:强制调用方管理指针,增加内存分配与空指针风险。

? 其他常见陷阱提醒

  • 不要忽略 json.Marshal 错误:你原代码中未检查 Encode 返回的 error,可能导致静默失败;
  • 避免在 MarshalJSON 中直接调用 json.Marshal(u):这会触发自身方法,造成栈溢出;
  • omitempty 与布尔字段:bool 类型本身不支持 omitempty(除非为 false),但你的逻辑已通过显式赋值控制输出,合理;
  • 字段名大小写敏感:确保结构体字段首字母大写(导出),否则 json 包无法访问。

✅ 最终验证建议

在单元测试中快速验证:

t.Run("marshal edition with nil fields", func(t *testing.T) {
    e := datamodel.Edition{Vault: nil, Online: new(bool)}
    data, _ := json.Marshal(e)
    // 断言输出是否含 "vault":false, "online":true
})

遵循方法集规则,明确接收器语义,是 Go 序列化定制的基石。选择值接收器通常更健壮、更易维护。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

js json

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

2025.06.09

3954

18

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

2025.07.04

4111

25

C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

2026.04.23

299

31

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

2023.10.19

3048

3

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

2025.10.17

4269

12

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2025.12.29

3689

9

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

2026.01.19

386

15

Go中Type关键字的用法
Go中Type关键字的用法

Go中Type关键字的用法有定义新的类型别名或者创建新的结构体类型。本专题为大家提供Go相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.06

2309

5

go怎么实现链表
go怎么实现链表

go通过定义一个节点结构体、定义一个链表结构体、定义一些方法来操作链表、实现一个方法来删除链表中的一个节点和实现一个方法来打印链表中的所有节点的方法实现链表。

2023.09.25

4547

6

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程