json.marshal不能直接替换为自定义序列化,因为json包仅识别签名严格的marshaljson/unmarshaljson方法,不支持其他函数名或参数类型;若接收者类型不匹配(如值传入但方法定义在指针上),接口不被满足,仍走默认反射逻辑,导致脱敏失效、时间错乱等问题。

为什么 json.Marshal 不能直接替换为自定义序列化
Go 的 json 包默认只认 MarshalJSON 和 UnmarshalJSON 方法,且要求签名严格匹配:func() ([]byte, error) 和 func([]byte) error。如果你试图用其他函数名(比如 MarshalMyProto)或不同参数类型,json 包完全无视——它不会自动 fallback 或协商协议。
常见错误现象是:结构体实现了自定义方法,但调用 json.Marshal 时仍走默认字段反射逻辑,导致敏感字段未脱敏、时间格式错乱、或嵌套结构被扁平化。
- 必须显式调用你的方法,不能依赖
json包自动发现 - 如果混用
json和自定义协议(如 msgpack、自研二进制),需统一入口,避免各处散落xxx.Marshal()调用 - 注意指针接收者 vs 值接收者:若结构体是值传入,而方法只定义在指针上,
json包会跳过该方法(不满足接口实现)
如何用 encoding.TextMarshaler / TextUnmarshaler 统一文本协议入口
Go 标准库提供 encoding.TextMarshaler 接口,比 json.Marshaler 更通用,适用于所有基于文本的序列化(json、yaml、toml 等)。只要实现它,就能被多数第三方库识别。
关键点在于:它返回 []byte,但语义是「文本表示」,不是原始字节流;因此适合用于带 schema 的文本协议(如带版本头的 JSON、base64 编码的 payload 等)。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
-
MarshalText返回的内容会被json包当作字符串字段值(除非你额外包装成json.RawMessage) - 若想保持结构体形态(而非转成字符串),必须在
MarshalText里先json.Marshal再加工,最后返回[]byte - 示例:
func (u User) MarshalText() ([]byte, error) { raw, _ := json.Marshal(u) // 添加协议头:v1|{...} return append([]byte("v1|"), raw...), nil }
二进制协议(如 Protobuf、自研格式)怎么和标准库共存
二进制协议无法通过 TextMarshaler 表达,必须另建抽象层。推荐做法是定义一个协议无关的序列化器接口,而不是把逻辑塞进结构体方法里:
type Serializer interface {
Marshal(v interface{}) ([]byte, error)
Unmarshal(data []byte, v interface{}) error
}
var JSONSerializer = &jsonSerializer{}
var MyBinarySerializer = &binarySerializer{}
这样做的好处是:协议逻辑与业务结构体解耦;可按场景注入不同实现(测试用 JSON,线上用 binary);也方便加中间件(如加密、压缩)。
- 不要在结构体里同时实现
MarshalJSON和MarshalBinary—— 容易混淆调用路径,且 Go 不支持方法重载 - 如果必须复用结构体字段逻辑,用组合而非继承:让
binarySerializer内部调用json.Marshal+ 字节重排,而不是重复写字段映射 - 注意
Unmarshal时的内存安全:二进制协议无 schema 校验,unsafe操作或固定偏移解析容易 panic,务必加长度检查
模块内协议切换时最容易忽略的兼容性陷阱
当模块从 JSON 切换到自定义协议时,最常被忽略的是零值处理和字段缺失策略。例如:
- JSON 中未出现的字段反序列化后为零值;但二进制协议若省略默认字段,反序列化后可能保留旧值(尤其是指针字段)
- time.Time 在 JSON 中默认输出 RFC3339 字符串,而自定义协议若存 UnixMs 整数,
Unmarshal后必须显式调用time.UnixMilli,否则时间错乱 - map 或 slice 类型若在协议中允许 null,但 Go 结构体字段声明为非指针,则
Unmarshal会 panic(如json: cannot unmarshal null into Go struct field X of type []string)
真正麻烦的不是写新协议,而是让新旧协议在同一个结构体上表现一致 —— 这需要在 Unmarshal 实现里补全零值逻辑,而不是依赖协议本身。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










