ctx.JSON 是最直接的 JSON 响应方式,但需配合统一响应结构体、显式状态码控制及中间件或 OnResponse 钩子实现真正标准化,避免字段不一致、状态码错误或时间格式问题。

用 ctx.JSON 是最直接的方式,但必须配合结构体和状态码控制
Iris 默认不强制返回统一格式,ctx.JSON 只是把 Go 值序列化成 JSON 并设 Content-Type: application/json,它不管字段名、错误包装或状态一致性。真正“统一响应”,得靠你自己定义响应结构体,并在每个 handler 里显式调用 ctx.JSON(statusCode, resp)。
常见错误是只写 ctx.JSON(200, map[string]interface{}{"data": x}),导致不同接口字段不一致(有的叫 result,有的叫 payload),前端难处理。
- 推荐定义一个全局响应结构体,比如:
type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data,omitempty"` } - 所有成功响应都走
ctx.JSON(200, Response{Code: 0, Message: "success", Data: data}) - 错误响应别只用
ctx.StatusCode(400)+ctx.JSON,要统一用非 200 状态码 + 同一结构体,例如:ctx.JSON(400, Response{Code: 40001, Message: "参数校验失败", Data: nil}) - 注意:Iris 的
ctx.JSON不会自动设置Status,你传什么 status 就是什么;不传默认是 200 —— 这点容易漏,导致 400 错误返回了 200 状态码
中间件统一拦截并包装返回,避免每个 handler 重复写 Response
手动在每个 handler 里 new Response 容易漏、难维护。更稳妥的做法是用中间件 + 自定义上下文字段,把业务数据先存起来,最后统一序列化。
典型做法是:定义一个 ctx.Values().Set("response", value),再挂一个 iris.Handler 在路由链末尾,检查是否有该值,有就渲染统一 JSON。
- 不要用
ctx.Next()后直接ctx.JSON,因为可能被后续 handler 覆盖或提前写出 - 推荐用
ctx.OnResponse钩子(Iris v14+):它在响应即将写出前触发,此时可安全替换 body 和 status - 示例:
app.Use(func(ctx iris.Context) { ctx.OnResponse(func(ctx iris.Context) { if v := ctx.Values().Get("response"); v != nil { resp, _ := v.(Response) ctx.StatusCode(resp.CodeToHTTP()) ctx.JSON(resp) } }) ctx.Next() }) - 注意
OnResponse不会改变已写出的响应(比如前面已调ctx.Text或ctx.Redirect),所以务必确保只有 JSON 路径走这套逻辑
错误处理必须走同一出口,否则 panic 或 ctx.StopExecution() 会绕过统一包装
Iris 的 ctx.StopExecution() 或未捕获 panic 会导致中间件里的 OnResponse 不执行,返回裸错误或空响应。统一响应的前提是所有错误路径都收敛到同一个 Response 构造点。
- 注册全局错误处理器:
app.OnErrorCode(iris.StatusNotFound, notFoundHandler),每个错误码 handler 都返回Response - 业务层主动抛错时,别用
panic,改用自定义 error 类型 +ctx.StopWithStatusJSON(400, Response{...}) -
ctx.StopWithStatusJSON是关键:它终止链路、设状态码、写 JSON 三合一,且会跳过后续中间件但保证当前响应生效 - 验证中间件中是否用了
ctx.Next()后又没处理错误 —— 比如 JWT 校验失败后只写日志没调StopWithStatusJSON,就会漏包
注意 json.Marshal 的零值行为和时间格式,否则前端解析出错
统一响应结构体里的 Data 字段是 interface{},看似灵活,但实际序列化时会暴露底层类型问题。比如 time.Time 默认转成 RFC3339,而很多前端期望 Unix 时间戳;nil 指针字段可能变成 null 而不是忽略。
- 如果业务数据含
time.Time,要么在结构体字段上加json:"xxx,string"(输出带引号的字符串),要么全局注册自定义json.Marshaler -
Data字段用json:",omitempty"不能过滤掉nil接口值 —— 它仍会输出"data": null。如需彻底省略,得在构造Response时判断:if data == nil { resp.Data = nil } else { resp.Data = data } - 别依赖
map[string]interface{}做Data,嵌套深了字段顺序乱、类型丢失(比如int64变float64),应尽量用具体结构体 - 测试时用
curl -i看真实 HTTP 状态码和 body,别只信 JSON 内容 —— 很多“统一响应”问题其实是 status 码没设对
统一响应最难的不是写代码,是让所有人(包括新来的同事)在每个新增接口里都记得调对函数、设对状态码、走对中间件链。一旦某条路由绕过包装逻辑,整个“标准”就破了。











