
本文介绍如何通过自定义 `marshaljson` 方法,将 go 结构体序列化为键名可变的 json 对象(如 `{"country": "abc"}`),而非固定字段名的格式,从而满足动态字段名的 api 响应需求。
在标准 Go 的 JSON 序列化中,结构体字段名会直接映射为 JSON 的键(key),例如 Name 和 Value 字段默认生成 {"Name":"Country","Value":"abc"}。但当业务需要将 Name 字段的运行时值作为 JSON 键(如 "Country")、Value 字段的值作为对应键的值(如 "abc")时,必须绕过默认行为,采用自定义序列化逻辑。
最简洁可靠的方案是为结构体实现 json.Marshaler 接口,即定义 MarshalJSON() ([]byte, error) 方法:
package main
import (
"encoding/json"
"fmt"
)
type xAxis struct {
Name string
Value string
}
// MarshalJSON 实现 json.Marshaler 接口
func (a xAxis) MarshalJSON() ([]byte, error) {
// 构造一个仅含一个键值对的 map:key = a.Name, value = a.Value
m := map[string]interface{}{a.Name: a.Value}
return json.Marshal(m)
}
func main() {
data := xAxis{Name: "Country", Value: "abc"}
b, _ := json.Marshal(data)
fmt.Println(string(b)) // 输出:{"Country":"abc"}
// 支持多组数据(需切片 + 自定义封装,见下文注意事项)
data2 := xAxis{Name: "Population", Value: "1400000000"}
b2, _ := json.Marshal(data2)
fmt.Println(string(b2)) // 输出:{"Population":"1400000000"}
}✅ 关键要点说明:
- map[string]interface{} 是 Go 中表达“任意键名 + 任意值”的天然载体,且 json.Marshal 能正确将其转为对象字面量;
- a.Name 必须为合法 JSON 键(非空字符串、不包含控制字符),建议在业务层校验或预处理;
- 该方法仅影响 json.Marshal 行为,不影响 json.Unmarshal —— 若需反向解析(即从 {"Country":"abc"} 还原为 xAxis),需额外实现 UnmarshalJSON,但通常动态键场景下反序列化需求较少;
- 若需批量输出多个此类对象(如 [{...}, {...}]),应使用切片 []xAxis,其每个元素仍会独立调用 MarshalJSON,无需额外适配。
⚠️ 注意事项:
- 不要将 Name 设为空字符串或重复值,否则会导致 JSON 键非法或覆盖;
- 避免在 MarshalJSON 中递归调用 json.Marshal(a)(会引发无限循环);
- 如需支持 nil 安全或嵌套结构,可在 map 构造前增加空值判断(例如 if a.Name == "" { a.Name = "default" })。
通过这一模式,你能在保持类型安全与结构清晰的同时,灵活输出符合前端或第三方系统要求的动态 JSON Schema。










