
本文介绍在 Go Web 开发中,如何通过自定义 MarshalJSON 和 UnmarshalJSON 方法,实现同一结构体字段在请求(输入)和响应(输出)中差异化序列化:如禁止客户端提交 ID、但保留其在响应中;接收 Email 但不在响应中返回。
本文介绍在 go web 开发中,如何通过自定义 `marshaljson` 和 `unmarshaljson` 方法,实现同一结构体字段在请求(输入)和响应(输出)中差异化序列化:如禁止客户端提交 id、但保留其在响应中;接收 email 但不在响应中返回。
在基于 Echo + GORM 的 Go Web 服务中,安全地分离数据输入与输出逻辑至关重要。直接复用同一结构体(如 User)处理请求和响应,容易引发安全隐患(如客户端篡改 ID)或信息泄露(如意外暴露敏感字段)。Go 原生的 json 标签虽支持基础控制(如 omitempty、-),但无法满足「输入禁用某字段、输出启用」或「输入启用、输出禁用」的双向精细化需求。
推荐方案是采用类型别名 + 自定义 JSON 序列化方法,既保持代码清晰性,又无需侵入业务逻辑或手动清空字段。
✅ 正确做法:使用 UserInput 与 UserOutput 类型别名
首先定义基础模型:
type User struct {
ID uint `json:"user_id"`
Email string `json:"email_address,omitempty" validate:"required,email"`
}然后分别定义面向输入和输出的专用类型:
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
// 输入专用:仅接受 Email,忽略 ID(即使传入也自动丢弃)
type UserInput User
func (u *UserInput) UnmarshalJSON(data []byte) error {
// 使用匿名结构体精确控制反序列化字段
var aux struct {
Email string `json:"email_address,omitempty" validate:"required,email"`
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
u.Email = aux.Email
u.ID = 0 // 强制重置,确保无法通过输入设置 ID
return nil
}
// 输出专用:仅返回 ID,完全隐藏 Email
type UserOutput User
func (u *UserOutput) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
ID uint `json:"user_id"`
}{
ID: u.ID,
})
}? 实际使用示例(Echo 路由)
e.POST("/users", func(c echo.Context) error {
var input UserInput
if err := c.Bind(&input); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "invalid input")
}
// 创建用户(GORM 自动分配 ID)
user := User{Email: input.Email}
if err := db.Create(&user).Error; err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
// 返回响应时使用 UserOutput,只含 user_id
return c.JSON(http.StatusCreated, UserOutput(user))
})✅ 优势总结:
-
安全可靠:
UserInput.UnmarshalJSON完全绕过ID字段解析,杜绝客户端伪造; -
职责分离:输入/输出逻辑解耦,避免
user.ID = 0或user.Email = ""等易遗漏的手动清理; -
零侵入 ORM:
User仍可直接用于 GORM 操作,不影响数据库映射; -
可扩展性强:可为不同接口(如管理员视图、公开视图)定义更多定制类型(如
UserAdminView)。
⚠️ 注意事项:
- 若需支持部分字段更新(如 PATCH),应额外实现
UnmarshalJSON以兼容omitempty语义; - 验证逻辑(如
validate:"required,email")建议统一放在UserInput的Validate()方法中,而非依赖json标签; - 生产环境务必配合
validator库进行结构体级校验,不可仅依赖 JSON 解析。
通过这种模式,你既能坚守“最小权限输入”与“最小信息输出”的安全原则,又能写出简洁、可维护、符合 Go 习惯的专业 API 层代码。

















