动态忽略敏感字段需绕开默认序列化:可用json.RawMessage预处理、实现MarshalJSON接口按上下文判断、或自定义tag+反射解析的SafeMarshal函数;避免中间件全局替换及map中转引发的类型与契约风险。

JSON序列化时如何动态忽略敏感字段
Go 的 json.Marshal 默认只认结构体标签 json:"-" 或 json:"name,omitempty",没法运行时决定“这次要不要藏手机号”。真要动态控制,得绕开默认序列化路径。
- 用
json.RawMessage预先处理敏感字段:把要脱敏的值替换成"***"或空字符串,再塞进结构体,避免反射干扰 - 实现
json.Marshaler接口:在MarshalJSON()方法里按上下文(比如用户权限、环境配置)判断是否隐藏idCard、phone等字段 - 别直接改原始结构体字段值——会影响后续业务逻辑;脱敏应是序列化层的事,和数据层隔离
用 struct tag 实现条件性隐藏(不依赖运行时逻辑)
如果脱敏规则固定(如测试环境全脱敏、生产环境仅对非管理员脱敏),可以用自定义 tag + 封装 marshal 函数,比硬写 MarshalJSON 更轻量。
- 定义新 tag,比如
json:"phone,redact:admin",表示“仅当当前用户不是 admin 时才脱敏” - 写一个通用
SafeMarshal(v interface{}, ctx map[string]interface{}) ([]byte, error),解析 tag 并按 ctx 决定留空或替换 - 注意:标准库不识别这种 tag,必须自己用
reflect解析;json:",omitempty"和自定义逻辑不能混用,否则行为不可控
第三方库选型:gjson + fastjson 不适合脱敏场景
有人想用 gjson 先解析再删字段,或用 fastjson 手动构造——这在 HTTP 响应中反而增加 GC 压力且易出错。
-
gjson.Get(jsonBytes, "user.phone").String()只读取,不帮你改原始 JSON;想脱敏还得拼新 JSON,容易漏字段、丢类型 -
fastjson的Parse+Set调用链长,对小对象性能反不如原生json.Marshal+ 接口重写 - 真正省事又安全的做法:用
map[string]interface{}中转,但仅限简单结构;嵌套深或含 slice 时类型断言极易 panic
HTTP middleware 层统一脱敏的陷阱
在 Gin/echo 的 middleware 里拦截 ResponseWriter 并修改 body,看似一劳永逸,实际踩坑密集。
立即学习“go语言免费学习笔记(深入)”;
- body 可能已被 gzip 压缩,直接字符串替换会破坏二进制流,返回乱码或 502
- 无法区分“这是用户数据”还是“这是错误响应”,
{"error":"xxx"}里的error字段也可能被误脱敏 - 更稳妥的做法:在 handler 返回前调用
redactUserResponse(resp),只处理明确标记为用户数据的结构体实例
脱敏不是加个 tag 就完事。最常被忽略的是:脱敏后字段类型是否还匹配前端契约?比如把 string 类型的 phone 换成 null,而前端仍按字符串解包,就会 crash。务必保证脱敏后的 JSON schema 与文档一致。










