必须显式实现binarymarshaler/binaryunmarshaler或格式专用接口(如marshalyaml)才能确保自定义类型被正确序列化/反序列化;binarymarshaler适用于完全掌控字节流、对接遗留协议或需版本头/magic number等二进制定制场景,而格式专用接口需注意接收者类型、嵌套内联、扩展注册及库版本兼容性。

Go 里想让自定义类型被正确序列化/反序列化,不能只靠结构体标签或反射自动推导——必须显式接入编解码器的控制链路。核心路径就两条:encoding.BinaryMarshaler/encoding.BinaryUnmarshaler(最轻量)或格式专用接口(如 MarshalYAML、MarshalMsgpack)。选错接口,轻则字段丢失,重则 panic 或静默失败。
什么时候该用 BinaryMarshaler/BinaryUnmarshaler
当你需要完全掌控字节流,且不依赖任何序列化库的类型系统时,这是首选。它不绑定 JSON/YAML/msgpack 等具体格式,只管“我怎么变成 []byte”和“[]byte 怎么变回我”。
- 协议含 magic number 或版本头(比如前 2 字节是
0x474f表示 Go 协议) - 要跳过零值字段、压缩字段名、做 LZ4 压缩等二进制层优化
- 对接遗留 C/C++ 服务,字段顺序和对齐必须严格匹配
- 不想引入第三方 codec 库,只用标准库
注意:MarshalJSON 里写二进制逻辑是无效的——json.Marshal 会强制加引号、转义、排序 key,根本不会调用你写的二进制逻辑。
YAML/Msgpack 等格式专用接口怎么注册才生效
实现 MarshalYAML 方法只是第一步,还得确保类型在实际编码时被识别。常见失效原因:
- 方法接收者是值类型(
func(d CustomDate) MarshalYAML()),但结构体字段是*CustomDate指针——指针不实现该方法 - 使用了嵌套结构体,外层没加
yaml:",inline",导致内层MarshalYAML不被触发 - msgpack 场景下漏掉
RegisterExt:光实现MarshalMsgpack没用,必须调用msgpack.RegisterExt(1, &MyType{}, myEncoder) - YAML 库用的是
gopkg.in/yaml.v3,但你的方法实现的是ya/yaml的MarshalYAML接口——两者不兼容
UnmarshalXXX 容易 panic 的三个硬伤
所有 UnmarshalXXX 方法都运行在边界输入上,网络或文件来的数据不可信。以下写法几乎必崩:
- 没校验输入长度就直接
data[0]或copy(dst, data[4:])——越界 panic - 把
uint8当作字符串长度,却没检查int(len) > len(data)-headerLen - 用
binary.Read读固定长度整数,但 buffer 不足——它不返回 error,而是直接 panic
安全写法:开头先 if len(data) ;读长度字段后,立刻校验后续内容是否够长;优先用 <code>binary.Uvarint 替代固定长度读取。
GORM 和 RPC 场景下的编解码器陷阱
GORM 的 SerializerInterface 和 net/rpc 的 ServerCodec 是两套独立体系,不能混用。
- GORM 中实现
Scan/Value时,dbValue可能是[]byte、string或nil,必须全分支处理,不能假设一定是[]byte - RPC 自定义
ServerCodec时,ReadRequestHeader必须解析帧头(含长度、ID、序列号),否则无法分帧——JSON 格式没有 gob 那样的内置分帧能力,自己不处理就会粘包 - 别在 GORM Serializer 里调
json.Marshal后直接存入 text 字段——UTF-8 字符可能被数据库截断,应存[]byte并设列为BLOB
真正难的不是写逻辑,而是判断该在哪一层接管:是数据库字段级(GORM)、协议帧级(RPC)、还是序列化格式级(YAML/Msgpack)?选错层级,后面全是补丁。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











