
本文详解 Go 语言中通过结构体标签(struct tags)控制 JSON 序列化行为,重点解决密码等敏感字段意外暴露的问题,涵盖 - 标签、omitempty 选项及嵌入结构体的正确处理方式。
本文详解 go 语言中通过结构体标签(struct tags)控制 json 序列化行为,重点解决密码等敏感字段意外暴露的问题,涵盖 `-` 标签、`omitempty` 选项及嵌入结构体的正确处理方式。
在 Go 的 encoding/json 包中,结构体字段是否出现在 JSON 输出中,完全由其导出状态(首字母大写)和 json 标签共同决定。仅靠匿名嵌入(如 A)无法覆盖父结构体字段的序列化行为——这正是原始代码失败的根本原因:B 中虽定义了 Password string \json:"-",但嵌入的A.Password` 仍会被独立序列化,导致敏感字段重复且未被屏蔽。
要真正隐藏敏感字段,必须确保该字段在最终参与序列化的结构体层级中不被 JSON 编码器识别为有效输出项。以下是两种推荐方案:
✅ 方案一:使用 - 标签彻底排除字段(推荐用于敏感字段)
type A struct {
Name string `json:"name"`
Password string `json:"-"` // 关键:直接禁止 A.Password 参与 JSON 编码
}
type B struct {
A
Locale string `json:"locale"`
}
func main() {
a := A{"Jim", "some_secret_password"}
b := B{A: a, Locale: "en"}
data, _ := json.Marshal(&b)
fmt.Println(string(data)) // 输出:{"name":"Jim","locale":"en"}
}⚠️ 注意:json:"-" 是最严格的方式,它会完全跳过该字段,无论其值是否为空。适用于密码、token、内部 ID 等绝不应出现在 API 响应中的字段。
✅ 方案二:显式覆盖并结合 omitempty(适用于可选字段)
若需保留字段名但仅在值为空时省略(如部分场景下允许空密码),可显式声明同名字段并使用 omitempty:
type A struct {
Name string `json:"name"`
Password string `json:"password"` // 保持原标签(仅作说明)
}
type B struct {
A
Password string `json:"password,omitempty"` // 覆盖 A.Password,空值时自动省略
Locale string `json:"locale"`
}
func main() {
a := A{"Jim", "some_secret_password"}
b := B{A: a, Locale: "en"}
// 注意:此时 b.Password 为零值 "",故 password 字段不会出现在 JSON 中
data, _ := json.Marshal(&b)
fmt.Println(string(data)) // 输出:{"name":"Jim","locale":"en"}
}? 原理:Go 的 JSON 编码器按字段声明顺序解析,后声明的同名字段会覆盖嵌入结构体中同名字段的标签行为(前提是字段名相同且可导出)。但此方式依赖 Password 在 B 中被显式赋值为空,不适用于需保留非空敏感值的场景——因为一旦 b.Password = "xxx",它就会被序列化,反而造成泄露。
? 关键总结与最佳实践
- 永远不要依赖“未导出字段”来保护敏感数据:JSON 编码器只处理导出字段,但若你误将敏感字段设为导出(如 Password string),就必须用 json:"-" 显式禁用。
- 嵌入结构体 ≠ 字段继承控制权:嵌入仅提供字段复用,不传递标签策略;每个字段的 JSON 行为由其最终所在结构体中的定义决定。
- 优先使用 json:"-" 处理敏感字段:比 omitempty 更安全、语义更清晰,杜绝任何意外输出可能。
- 额外建议:对高敏感字段(如密码),还可配合自定义 MarshalJSON 方法实现加密或占位符逻辑,进一步增强安全性。
遵循以上原则,即可在保持代码简洁性的同时,确保 API 响应中绝不意外暴露敏感信息。


















