json.Marshal 从不 panic,只返回 error;真正导致崩溃的是忽略 error 后使用 nil 切片、空指针或类型断言失败,正确做法是立即检查并处理 error。

为什么 json.Marshal 会 panic?它其实不会
很多人以为 json.Marshal 可能 panic,实际它**从不 panic**,而是返回 error。真正让你程序崩溃的,往往是忽略这个 error 后继续用 nil 的字节切片(比如传给 HTTP 响应或写文件),或在后续逻辑中触发空指针/类型断言失败。
-
json.Marshal只在遇到不可序列化值时返回非nil错误,例如含func、chan、未导出字段且无jsontag、循环引用等 - 错误类型是
*json.UnsupportedTypeError或*json.InvalidUTF8Error等,都实现了error接口,可直接判断 - 不要用
recover()拦截json.Marshal—— 它根本不会抛出 panic
正确处理 json.Marshal 错误的惯用写法
Go 中标准做法是「检查 error,立刻处理」。常见错误是只打印日志却不终止流程,导致下游收到空数据或乱码。
data := struct {
Name string `json:"name"`
Age int `json:"age"`
Fn func() `json:"-"` // 不导出 + 无 tag → 会出错
}{
Name: "Alice",
Age: 30,
}
b, err := json.Marshal(data)
if err != nil {
// ✅ 正确:区分错误类型,做针对性响应
switch e := err.(type) {
case *json.UnsupportedTypeError:
log.Printf("无法序列化字段 %s: %v", e.Type.String(), e.Error())
http.Error(w, "响应数据构造失败", http.StatusInternalServerError)
return
case *json.InvalidUTF8Error:
log.Printf("字符串含非法 UTF-8: %v", e)
http.Error(w, "数据编码异常", http.StatusBadRequest)
return
default:
log.Printf("JSON 序列化未知错误: %v", err)
http.Error(w, "服务内部错误", http.StatusInternalServerError)
return
}
}
w.Header().Set("Content-Type", "application/json")
w.Write(b)
哪些值会导致 json.Marshal 返回 error?
不是所有 Go 类型都能直译成 JSON。以下情况必然失败,且错误信息明确:
- 含有
func、chan、complex64/complex128类型字段(即使已导出) - 结构体字段未导出(首字母小写)且没加
json:"..."tag;若加了json:"-"则跳过,不报错 - map 的 key 类型不是
string、int、int32等基本可比较类型(如map[struct{X int}]string) - 字符串字段包含非法 UTF-8 字节(例如从二进制读取后未校验就赋值)
- 嵌套结构体存在循环引用(A 包含 B,B 又包含 A 指针)—— 这会触发
json: unsupported value: encountered a cycle
测试和调试 json.Marshal 错误的实用技巧
线上环境难复现循环引用或非法 UTF-8,建议在单元测试中主动构造边界 case。
立即学习“go语言免费学习笔记(深入)”;
- 用
reflect.ValueOf(v).Kind() == reflect.Func提前过滤掉含函数字段的结构体(适合封装自己的SafeMarshal) - 对用户输入的字符串,用
utf8.Valid([]byte(s))预检,避免传入json.Marshal后才报错 - 开启
go test -v并在测试中故意传入含chan int的结构体,确认错误分支被覆盖 - 注意:
json.Marshal(nil)是合法的,返回字面量null,不会出错
最常被忽略的是:错误处理后忘记 return,导致仍执行后续 w.Write(nil) —— 这会让客户端收到空响应而不报错,排查难度远大于直接返回 500。










