json.marshal 不会静默失败,必返回非 nil error;空字节通常因忽略 error、字段未导出、含 json:"-" 标签、值为空且 omitempty、或类型不支持(如 func、map 非字符串 key、循环引用)导致。

为什么 json.Marshal 会静默失败但返回空字节?
它不会“静默失败”——只要出错,json.Marshal 一定返回非 nil 的 error。你看到空结果,大概率是忽略了返回的 error,还继续用了 nil 的 []byte。
- 常见错误现象:
data, _ := json.Marshal(v)—— 下划线吞掉错误,后续string(data)是空串,HTTP 响应发了个空 body - 结构体字段没加导出标记(首字母小写),比如
type User { name string },json.Marshal序列化后是{} - 字段有
json:"-"标签,或json:"name,omitempty"且值为空,也会导致字段消失,不是错误,但结果不符合预期 -
time.Time、func、chan、map中含不可序列化 key(如map[struct{}]*int)会直接报json: unsupported type
哪些类型会触发 json.Marshal 明确报错?
Go 的 json 包只支持有限类型。一旦遇到不支持的,错误明确且不可忽略。
-
func类型:报json: unsupported type: func() - 未导出结构体字段(小写开头):不报错,但不输出,容易误以为“序列化成功”
-
map的 key 是非字符串/非基本类型(如map[interface{}]string或map[MyStruct]int)→json: unsupported type: MyStruct -
nil指针字段本身不报错,但如果指针指向不支持类型(如*func()),仍会报错 - 循环引用(A 包含 B,B 又包含 A)→
json: invalid recursive type,Go 1.20+ 才支持检测,旧版本可能 panic
怎么安全地调试 json.Marshal 异常?
别靠打印结果猜,要从输入源头和错误路径入手。
- 永远检查
error:data, err := json.Marshal(v); if err != nil { log.Printf("marshal failed: %+v", err); return } - 用
fmt.Printf("%#v", v)看原始值,比println(v)更清楚字段是否导出、是否为nil - 对结构体启用
json.RawMessage做分段验证:先把子字段单独Marshal,缩小问题范围 - 注意
time.Time默认序列化成 RFC3339 字符串;如果自定义了MarshalJSON方法但忘了返回[]byte和error,会 panic - 测试时用
json.Valid(data)验证输出是否合法 JSON(尤其对接前端时,避免因空字段漏掉引号导致 JS 解析失败)
性能与兼容性:什么时候该换方案?
json.Marshal 快,但不是万能的。有些场景它既慢又易错,得换思路。
立即学习“go语言免费学习笔记(深入)”;
- 高频小对象序列化(如日志行):原生
json包反射开销明显,考虑easyjson或ffjson生成静态代码(但维护成本上升) - 需要兼容旧版 Go(json.Number 默认关闭,数字可能被转成 float64 导致精度丢失,需显式设置
Decoder.UseNumber() - 嵌套很深或字段极多的结构体:反射深度遍历可能栈溢出(少见),可改用
jsoniter并调低MaxDepth - 想保留
nilslice/map 为null而非[]/{}:原生不支持,得自己实现MarshalJSON,或用gjson+ 手动构造
最常被忽略的是:错误处理写在 if 里,但 panic 没捕获,上游 HTTP handler 一崩整个连接就断——json.Marshal 本身不 panic,但你的自定义 MarshalJSON 方法会。










