Go 中自定义错误类型的 JSON 序列化实现

在 go 构建 json api 时,需将错误以标准 json 格式(如 {"error": "message"})返回;直接嵌入 error 字段无法自动序列化,须显式实现 json.marshaler 接口。

要让自定义错误类型(如 JsonErr)在调用 json.Marshal 或 json.NewEncoder.Encode 时输出结构化的 JSON 错误响应,关键在于正确实现 json.Marshaler 接口,而非依赖 error 接口本身——因为 encoding/json 包不会对 error 类型做特殊处理,且大多数 error 实现(如 errors.New 返回的 *errors.errorString)内部字段非导出,反射无法访问。

推荐做法是采用内嵌 error 接口 + 自定义 MarshalJSON() 方法的方式,简洁且符合 Go 惯例:

type JsonErr struct {
    error // 内嵌 error,自动获得 Error() 方法
}

func (e JsonErr) MarshalJSON() ([]byte, error) {
    // 安全转义错误消息,避免 JSON 注入(生产环境务必使用)
    msg := strings.ReplaceAll(e.Error(), `"`, `\"`)
    return []byte(`{"error": "` + msg + `"}`), nil
}

⚠️ 注意:上述字符串拼接仅作演示,实际项目中应使用 json.Marshal 处理消息字段以确保 JSON 安全性

func (e JsonErr) MarshalJSON() ([]byte, error) {
    type Alias JsonErr // 防止无限递归(避免再次调用本方法)
    return json.Marshal(struct {
        Error string `json:"error"`
    }{
        Error: e.Error(),
    })
}

这样既保持了类型清晰,又利用了标准库的转义与格式化能力。使用示例如下:

func handler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusBadRequest)
    json.NewEncoder(w).Encode(JsonErr{errors.New("Invalid request syntax")})
}
// 输出:{"error": "Invalid request syntax"}

✅ 总结:

  • 不要试图通过结构体标签(如 json:"error")直接 marshal error 字段——它会变成空对象 {};
  • 必须实现 MarshalJSON() ([]byte, error);
  • 优先使用 json.Marshal 序列化内部字段,而非手动拼接字符串;
  • 进一步扩展 JsonErr 支持状态码、详情字段等,只需在匿名结构体中添加对应字段即可。