17370845950

如何在Golang中捕获JSON序列化错误_Golangjson.Marshal错误处理示例
json.Marshal 从不 panic,只返回 error;真正导致崩溃的是忽略 error 后使用 nil 切片、空指针或类型断言失败,正确做法是立即检查并处理 error。

为什么 json.Marshal 会 panic?它其实不会

很多人以为 json.Marshal 可能 panic,实际它**从不 panic**,而是返回 error。真正让你程序崩溃的,往往是忽略这个 error 后继续用 nil 的字节切片(比如传给 HTTP 响应或写文件),或在后续逻辑中触发空指针/类型断言失败。

  • json.Marshal 只在遇到不可序列化值时返回非 nil 错误,例如含 funcchan、未导出字段且无 json tag、循环引用等
  • 错误类型是 *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。以下情况必然失败,且错误信息明确:

  • 含有 funcchancomplex64/complex128 类型字段(即使已导出)
  • 结构体字段未导出(首字母小写)且没加 json:"..." tag;若加了 json:"-" 则跳过,不报错
  • map 的 key 类型不是 stringintint32 等基本可比较类型(如 map[struct{X int}]string
  • 字符串字段包含非法 UTF-8 字节(例如从二进制读取后未校验就赋值)
  • 嵌套结构体存在循环引用(A 包含 B,B 又包含 A 指针)—— 这会触发 json: unsupported value: encountered a cycle

测试和调试 json.Marshal 错误的实用技巧

线上环境难复现循环引用或非法 UTF-8,建议在单元测试中主动构造边界 case。

  • 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。