json.NewEncoder更安全,因它流式编码、自动错误传播、省内存;手动json.Marshal易漏错误检查、触发OOM或响应截断,且需手动设Content-Type和状态码。

为什么直接用 json.NewEncoder 写响应体比手动 json.Marshal 更安全
因为 json.NewEncoder 会自动处理流式写入、错误传播和 Content-Type 设置(需手动设),而手动 json.Marshal 容易漏掉错误检查,或在大结构体时吃光内存——它得先把整个 JSON 字符串拼好再写,NewEncoder 则边序列化边写入 http.ResponseWriter,适合任意大小的数据。
常见错误现象:http: request body too large 或响应为空但没报错,往往是因为 Marshal 出错被忽略,或 WriteHeader 调用时机不对。
- 必须在调用
Encode前设置Content-Type: application/json,否则浏览器可能解析失败 -
http.ResponseWriter是io.Writer,可直接传给json.NewEncoder,无需中间缓冲 - 如果
Encode返回非 nil 错误,响应已部分写出,无法再调用http.Error;应尽早检查并返回
如何正确调用 json.NewEncoder(w).Encode(v)
这是最简可行路径。关键不是“怎么写”,而是“在哪写”和“写完后能不能再改状态”。
- 先调
w.Header().Set("Content-Type", "application/json"),推荐放在 handler 开头 - 直接
err := json.NewEncoder(w).Encode(data),不要用Must或忽略 err - 若
err != nil,说明写入过程出问题(如客户端断连、结构体含不可序列化字段),此时不能重试或补写,只能记录日志并返回 - 不需要手动调
w.WriteHeader(http.StatusOK)——Encode内部会在首次写入时自动触发 200 状态码;但如果你需要非 200(如 404),必须在Encode前显式调用w.WriteHeader
json.Encoder 的常见陷阱:nil 指针、time.Time、自定义 marshaler
它不像 json.Marshal 那样能提前预检结构体合法性,错误会在写入中途爆发,导致响应截断。
- 嵌套
*string为nil?合法,输出null,没问题 -
time.Time默认输出 RFC3339 字符串,但若字段是未初始化的零值,也会正常输出,不 panic - 如果结构体实现了
json.Marshaler,Encode会调用它;但如果实现有 bug(比如死循环或 panic),错误会直接暴露给Encode返回的err - 注意:
Encoder不支持json.RawMessage的延迟编码逻辑,它会把 raw bytes 当普通字节流原样写出,不校验 JSON 合法性
什么时候不该用 json.NewEncoder 直接写响应体
不是所有场景都适合流式写入。比如你要做响应体压缩(gzip)、审计日志(需完整 JSON 字符串)、或统一错误包装(所有错误都走 { "error": "..." })。
- 需要 gzip:用
gzip.NewWriter(w)包一层,再传给json.NewEncoder,但注意Close()必须调用,且不能在 handler 返回前遗漏 - 要记录原始 JSON:只能先
json.Marshal到[]byte,再写入,但务必检查err,且别忘了w.Header().Set("Content-Length", ...) - 统一错误格式:建议封装一个
writeJSON(w, status, v interface{})辅助函数,内部处理 header、status、encode 和 error 分发
真正容易被忽略的是:一旦开始写响应体(无论用 Encode 还是 Write),就再也不能修改 HTTP 状态码或 header。很多调试时加的 log.Printf 放在 Encode 后面,其实已经晚了——响应头早就发出去了。











