Go JSON编解码要求字段导出且带json标签,路由须严格按HTTP方法区分,响应需统一格式并显式设状态码与Content-Type,模块化应优先组合与函数选项模式。

结构体字段必须导出且带json标签
Go 的 encoding/json 包只序列化/反序列化首字母大写的导出字段。如果定义了 name string(小写),json.Unmarshal 会静默跳过它,请求体看似解析成功,实则字段为空。
正确写法是:
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
- 所有参与 JSON 编解码的字段必须大写开头
-
json:标签值要与前端实际字段名一致(如 API 文档约定为user_name,就写json:"user_name") - 可加
omitempty控制零值不输出:Age int `json:"age,omitempty"`
路由必须严格绑定 HTTP 方法,不能靠 URL 后缀或 query 判断
RESTful 的语义核心在 HTTP 方法,不是路径字符串。写 GET /api/user/delete?id=123 是典型反模式——它绕过中间件、无法被 OpenAPI 工具识别、也违背幂等性原则。
用 net/http 做基础封装时,务必在 handler 开头校验方法:
func handleUser(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// 获取单个或列表
case http.MethodPost:
// 创建
case http.MethodPut:
// 全量更新(要求客户端提供完整资源)
case http.MethodPatch:
// 部分更新(需额外解析 patch 操作)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
}
- 不要用
r.URL.Query().Get("action")分支逻辑 - PUT 必须能完整替换资源,不能只更新部分字段;否则该用 PATCH
- DELETE 请求通常不带请求体,别试图
json.Decode(r.Body)
响应必须统一格式并显式设状态码和 Content-Type
直接 json.NewEncoder(w).Encode(v) 不够——它不设状态码,也不写 Content-Type 头,某些客户端(如 Axios)可能因缺失 Content-Type 无法自动解析 JSON。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
推荐封装一个 writeJSON 函数:
func writeJSON(w http.ResponseWriter, status int, v interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
json.NewEncoder(w).Encode(v)
}
- 状态码必须匹配语义:
http.StatusCreated(201)用于 POST 成功,http.StatusOK(200)用于 GET - 错误响应别用
http.Error返回纯文本(它设的是text/plain),生产环境应走统一 JSON 错误结构 - 避免多次调用
w.WriteHeader,Go 会 panic
模块化时优先组合而非继承,用函数选项模式控制行为
封装 REST 模块不是写一个“万能 Router”,而是把可复用逻辑拆成独立函数:参数校验、JSON 解析、响应包装、错误转换。Golang 没有继承,靠组合 + 接口 + 函数参数更自然。
例如支持可选的 JSON body 解析和验证:
type HandlerOption func(*handlerConfig)
func WithValidation(fn func(interface{}) error) HandlerOption {
return func(c *handlerConfig) { c.validate = fn }
}
func NewUserHandler(opts ...HandlerOption) http.HandlerFunc {
cfg := &handlerConfig{}
for _, opt := range opts {
opt(cfg)
}
return func(w http.ResponseWriter, r *http.Request) {
var u User
if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid json"})
return
}
if cfg.validate != nil {
if err := cfg.validate(&u); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
}
// ...
}
}
- 避免把所有逻辑塞进一个 struct 里,保持 handler 纯净
- 中间件(如 JWT 验证、CORS)应独立于业务 handler,用
func(http.Handler) http.Handler形式组合 - 路径参数提取、query 解析这些通用操作,应单独抽成工具函数,不耦合在 handler 内
真正的难点不在语法,而在对 HTTP 语义的理解是否一致——比如什么时候该用 404 而不是 400,PUT 和 PATCH 的边界是否清晰,以及是否愿意为一致性放弃“快速实现”的捷径。模块封装的价值,是让这些判断变成代码里的强制约束,而不是每次 review 时口头提醒。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










