
本文详解如何在 go 中通过字段遮蔽(field shadowing)与 json 标签组合,安全地控制嵌入结构体(如 gorm 的 gorm.model)在 json 序列化时的字段可见性,避免暴露 deletedat 等内部状态字段。
本文详解如何在 go 中通过字段遮蔽(field shadowing)与 json 标签组合,安全地控制嵌入结构体(如 gorm 的 gorm.model)在 json 序列化时的字段可见性,避免暴露 deletedat 等内部状态字段。
在使用 GORM 构建 REST API 或导出数据为 JSON 时,常需将数据库模型(如嵌入 gorm.Model 的 User)转换为对外公开的结构体。但直接嵌入会导致 ID、CreatedAt、DeletedAt 等 GORM 内部字段一并序列化——即使你已为 Password 设置了 json:"-" 或 omitempty,DeletedAt *time.Time 仍会以 "DeletedAt": null 形式出现,既冗余又可能泄露软删除逻辑。
根本原因在于:Go 的结构体嵌入是字段继承而非类型隔离;当 PublicUser 嵌入 *User,而 User 又嵌入 gorm.Model 时,DeletedAt 字段自动提升至 PublicUser 的顶层字段集,其原始 json 标签(若未显式声明则默认为字段名)无法被外层结构体“覆盖”。
✅ 正确解法是字段遮蔽(Field Shadowing):在公开结构体中重新声明同名字段,并赋予更合适的类型与 JSON 标签。例如将 *time.Time 遮蔽为 bool,并用 omitempty 实现条件省略:
// 数据库模型(含 gorm.Model)
type User struct {
gorm.Model
Username string `json:"username" sql:"size:32;not null;unique"`
Password string `json:"-" sql:"not null"` // 完全忽略 JSON 输出
Locale string `json:"locale" sql:"not null"`
}
// 公开视图模型:遮蔽敏感/内部字段
type PublicUser struct {
*User
DeletedAt bool `json:"deleted_at,omitempty"` // 遮蔽 gorm.Model.DeletedAt,不输出 null
Password bool `json:"password,omitempty"` // 遮蔽 User.Password,确保不泄露
}运行后,json.Marshal 或 json.NewEncoder 将:
- 忽略原始 gorm.Model.DeletedAt(因被 bool DeletedAt 遮蔽);
- 不输出 deleted_at 字段(因 bool 默认为 false,omitempty 生效);
- 完全跳过 password(bool 值恒为 false,且 omitempty 触发省略)。
⚠️ 注意事项:
- 遮蔽字段必须与原字段同名(大小写敏感),否则无效;
- 类型可不同(如 *time.Time → bool),但语义需清晰(bool 表示“是否已删除”比 null 更直观);
- 若需保留时间信息,可用 time.Time 遮蔽并自定义 json 标签:DeletedAt time.Timejson:"deleted_at,omitempty";此时需确保赋值逻辑正确(如u.DeletedAt.Valid` 判断);
- 切勿依赖 json:"-" 在嵌入结构体上生效——它只作用于直接声明的字段,对嵌入链中的字段无效;
- GORM v2(GORM.io)已改用 gorm.Model 新实现,但遮蔽原理完全一致。
总结:Go 的结构体嵌入不提供字段访问控制,JSON 序列化行为由最终可见字段的标签决定。通过主动遮蔽 + 合理标签(omitempty / - / 自定义键名),即可精准掌控 API 输出结构,兼顾安全性、可读性与向后兼容性。










