
本文介绍在 Go 结构体嵌套场景下,如何通过 JSON 标签精准控制字段序列化行为,避免敏感字段(如 Password)意外暴露在 JSON 输出中,核心方法是合理使用 json:"-" 和 json:",omitempty" 等结构体标签。
本文介绍在 go 结构体嵌套场景下,如何通过 json 标签精准控制字段序列化行为,避免敏感字段(如 `password`)意外暴露在 json 输出中,核心方法是合理使用 `json:"-"` 和 `json:",omitempty"` 等结构体标签。
在 Go 的 JSON 序列化过程中,结构体字段是否出现在最终 JSON 字符串中,完全由其导出状态(首字母大写)和 json struct tag 决定。当使用匿名嵌入(如 A 嵌入到 B)时,嵌入类型的字段会“提升”为外层结构体的字段——这意味着 B 会自动拥有 Name 和 Password 两个可导出字段,且它们的原始 tag(json:"name" 和 json:"password")依然生效。因此,即使你在 B 中额外声明了一个同名字段 Password string \json:"-"`,它不会覆盖嵌入字段 A.Password 的序列化行为,导致敏感密码仍被输出。
正确做法不是用 - 屏蔽新字段,而是覆盖嵌入字段的 JSON 行为:在 B 中重新声明同名字段,并赋予其期望的 JSON 标签(如 json:"-" 或 json:",omitempty"),从而屏蔽或条件性排除原嵌入字段。
以下是一个安全、简洁且符合 Go 惯例的实现:
package main
import (
"encoding/json"
"fmt"
)
type A struct {
Name string `json:"name"`
Password string `json:"password"` // 原始定义,但会被 B 中的同名字段覆盖
}
type B struct {
A
Password string `json:"-"` // ✅ 关键:显式屏蔽 Password 字段,优先级高于嵌入字段
Locale string `json:"locale"`
}
func main() {
a := A{"Jim", "some_secret_password"}
b := B{A: a, Locale: "en"}
data, err := json.Marshal(&b)
if err != nil {
panic(err)
}
fmt.Println(string(data)) // 输出:{"name":"Jim","locale":"en"}
}? 原理说明:Go 的结构体字段解析遵循“就近原则”。当 B 同时包含嵌入字段 A.Password 和显式字段 B.Password 时,json.Marshal 会优先使用 B.Password 的 tag(json:"-"),从而彻底跳过该字段的序列化。注意:B.Password 无需赋值,只要声明即可生效。
⚠️ 注意事项:
- 不要依赖 omitempty 隐藏敏感字段(如 json:"password,omitempty")——因为非空密码字符串永远不满足“empty”条件("" 才是 empty),该方案无效;
- 若需保留 Password 字段用于内部逻辑(如校验),但仅禁止 JSON 输出,请坚持使用 json:"-";
- 对于 API 响应,建议采用专用响应结构体(DTO 模式),而非复用含敏感字段的业务结构体,实现关注点分离;
- 如需同时支持读写不同字段(如创建时接收密码、响应时不返回),可定义两个结构体,或使用 json.RawMessage + 自定义 MarshalJSON 方法实现更精细控制。
综上,json:"-" 是隐藏敏感字段最直接、可靠且语义清晰的方式,配合结构体嵌入机制,能以零运行时代价实现数据脱敏。


















