
本文详解在 Go 中使用 GORM 等 ORM 库时,如何通过结构体嵌入与字段遮蔽(field shadowing)技术,精准控制 JSON 序列化行为——特别是隐藏 DeletedAt 等框架内置但不应暴露的字段。
本文详解在 go 中使用 gorm 等 orm 库时,如何通过结构体嵌入与字段遮蔽(field shadowing)技术,精准控制 json 序列化行为——特别是隐藏 `deletedat` 等框架内置但不应暴露的字段。
在 Go 的结构体嵌入(embedding)场景中,若直接嵌入 gorm.Model(含 ID、CreatedAt、UpdatedAt、DeletedAt 等字段),这些字段默认会参与 JSON 编码,即使你希望对外仅暴露业务字段(如 username、locale),且明确屏蔽软删除时间戳 DeletedAt 和密码等敏感字段,标准标签(如 json:"-")也无法直接作用于嵌入字段——因为嵌入是匿名的,无法单独为 gorm.Model.DeletedAt 添加标签。
此时,字段遮蔽(Field Shadowing) 是最简洁、可靠且符合 Go 惯用法的解决方案:在导出结构体中定义一个同名字段(类型可不同),它将覆盖(shadow)嵌入结构体中对应名称的字段,并允许你自由指定 JSON 行为。
以下是一个生产就绪的示例:
package main
import (
"encoding/json"
"fmt"
"os"
"time"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
// 数据库模型:嵌入 gorm.Model
type User struct {
gorm.Model
Username string `json:"username" sql:"size:32;not null;unique"`
Password string `json:"password" sql:"not null"`
Locale string `json:"locale" sql:"not null"`
}
// 公共 API 响应模型:通过遮蔽控制序列化
type PublicUser struct {
*User
// 遮蔽 gorm.Model.DeletedAt:用 bool 类型 + omitempty 实现“完全不出现”
DeletedAt bool `json:"deleted_at,omitempty"`
// 同时遮蔽 Password 字段(即使 User 中已有 json:"password",此处优先级更高)
Password bool `json:"-"` // 或设为 `json:"password,omitempty"` 并保持 false 值
}
func main() {
db, err := gorm.Open("sqlite3", ":memory:")
if err != nil {
panic(err)
}
defer db.Close()
// 初始化表并插入测试数据
db.AutoMigrate(&User{})
db.Create(&User{
Username: "dan",
Password: "secret123",
Locale: "en_US",
})
var u User
db.First(&u, 1)
// 序列化为 PublicUser
pub := PublicUser{
User: &u,
// DeletedAt 默认为 false → 因 omitempty 不出现在 JSON 中
// Password 默认为 false → 因 json:"-" 完全忽略
}
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
encoder.Encode(pub)
}输出结果(注意:DeletedAt 和 Password 字段均未出现):
{
"ID": 1,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"username": "dan",
"locale": "en_US"
}✅ 关键原理说明:
- Go 结构体字段查找遵循「就近原则」:当 PublicUser 同时包含嵌入的 *User 和显式声明的 DeletedAt 字段时,json.Marshal 会优先使用显式字段;
- bool 类型的 DeletedAt 字段值为 false,配合 omitempty 标签,使该键在 JSON 中彻底消失;
- 此方法不侵入原始模型(User 保持纯净),符合单一职责原则,也便于多端复用(如 AdminUser 可遮蔽不同字段)。
⚠️ 注意事项:
- 遮蔽字段必须与被遮蔽字段同名(大小写敏感),类型可灵活选择(bool、string、甚至 *time.Time),但需确保零值语义合理;
- 若需保留 DeletedAt 的存在性(如标记是否已软删除),可改用 *bool 并手动赋值 &u.DeletedAt != nil;
- 避免在遮蔽字段上使用 json:",omitempty" 以外的复杂逻辑(如自定义 MarshalJSON)——除非有特殊需求,否则易引入维护成本;
- GORM v2(gorm.io/gorm)中 Model 结构略有变化,但遮蔽策略完全通用。
总结:面对嵌入结构体的 JSON 控制难题,字段遮蔽不是 hack,而是 Go 类型系统的自然表达。它轻量、无反射、零运行时开销,是构建清晰、可维护 API 响应层的推荐实践。










