如何在 Go 中为同时包含导出与非导出字段的结构体实现自定义 JSON 编解码

10次阅读

如何在 Go 中为同时包含导出与非导出字段的结构体实现自定义 JSON 编解码

gojson 包默认忽略非导出(小写开头)字段,但可通过实现 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 编解码的标准、安全且可扩展的方案。

text=ZqhQzanResources