
go 的 json 包默认忽略非导出(小写开头)字段,但可通过实现 marshaljson 和 unmarshaljson 方法,结合中间结构体,安全地序列化/反序列化混合可见性字段,避免递归调用栈溢出。
在 Go 中,JSON 编解码器仅能访问结构体的导出字段(即首字母大写的字段)。像 fieldA string 这样的非导出字段,默认不会参与 json.Marshal 或 json.Unmarshal。若需保留其 JSON 表现力,必须显式实现 json.Marshaler 和 json.Unmarshaler 接口——但关键在于:*绝不能在自定义方法中直接对 `Test调用json.Marshal/json.Unmarshal**,否则会触发无限递归(正如原问题中因嵌入*Test` 导致的栈溢出)。
推荐做法是定义一个纯导出、字段一一对应、仅用于 JSON 传输的中间结构体(如 TestJSON),它不包含任何业务逻辑,仅作为序列化桥梁:
type Test struct {
fieldA string // 非导出,需手动处理
FieldB int // 导出,可直接访问
FieldC string // 导出
}
// TestJSON:仅用于 JSON 编解码的导出结构体,字段名与 JSON key 对齐
type TestJSON struct {
FieldA string `json:"fieldA"`
FieldB int `json:"fieldB"`
FieldC string `json:"fieldC"`
}
func (t *Test) MarshalJSON() ([]byte, error) {
// 将当前 Test 实例的各字段显式赋值给 TestJSON,再序列化
return json.Marshal(TestJSON{
FieldA: t.fieldA,
FieldB: t.FieldB,
FieldC: t.FieldC,
})
}
func (t *Test) UnmarshalJSON(b []byte) error {
var temp TestJSON
if err := json.Unmarshal(b, &temp); err != nil {
return err
}
// 安全反向赋值:所有字段均为可写导出字段或本结构体内存可访问的非导出字段
t.fieldA = temp.FieldA
t.FieldB = temp.FieldB
t.FieldC = temp.FieldC
return nil
}✅ 优势说明:
- 无递归风险:TestJSON 是独立类型,与 Test 无嵌入或指针循环依赖;
- 可维护性强:新增字段时,只需同步更新 TestJSON 定义及两个方法中的字段映射,IDE 可辅助检测遗漏;
- 语义清晰:分离了数据模型(Test)与序列化契约(TestJSON),符合关注点分离原则。
⚠️ 注意事项:
- 若 Test 含嵌套结构体或切片,TestJSON 中对应字段也需保持相同导出性与 JSON tag;
- 不要尝试用 unsafe 或反射绕过导出限制——既破坏安全性,又丧失编译期检查;
- 若非导出字段本质是内部状态(如缓存、锁、连接句柄),则不应参与 JSON 序列化,此时应重新评估设计:是否真需暴露?能否用 json:"-" 显式忽略?或改用导出字段 + json tag 控制别名?
综上,使用专用中间结构体是 Go 中处理混合可见性字段 JSON 编解码的标准、安全且可扩展的方案。










