如何在 Go 中正确使用 JSON 序列化结构体切片

go 的 `json.marshal` 默认仅导出首字母大写的字段;若结构体字段为小写(未导出),序列化结果将为空对象,需通过标签和导出字段确保数据正确输出。

在 Go 中,JSON 编码(marshalling)遵循严格的可见性规则:只有导出字段(即首字母大写的字段)才能被 encoding/json 包访问并序列化。你定义的 SpanInfo 结构体中所有字段均为小写(如 imsi、network),属于未导出字段,因此 json.Marshal 无法读取其值,最终生成 [{},{},{},{}] 这样的空对象数组。

要解决此问题,需两步改造:

  1. 将字段名改为首字母大写,使其可导出
  2. 配合 json 标签(tag)指定序列化后的键名,保持 JSON 字段命名风格(如 snake_case)不变。

修正后的结构体定义如

下:

type SpanInfo struct {
    IMSI         string `json:"imsi"`
    Network      string `json:"network"`
    NetworkStatus string `json:"network_status"`
    SignalQuality int    `json:"signal_quality"`
    Slot         int    `json:"slot"`
    State        string `json:"state"`
}

type GatewayInfo []SpanInfo

此时,即使 GatewayInfo 是类型别名([]SpanInfo),只要其元素类型 SpanInfo 的字段已正确导出并标注,json.Marshal 即可完整序列化整个切片:

func getGatewayInfo(spans []SpanInfo) GatewayInfo {
    return GatewayInfo(spans)
}

// 使用示例
gatewayInfo := getGatewayInfo([]SpanInfo{
    {IMSI: "652025105829193", Network: "20801", NetworkStatus: "Registered (Roaming)", SignalQuality: 17, Slot: 2, State: "active"},
    {IMSI: "652025105829194", Network: "20801", NetworkStatus: "Registered (Roaming)", SignalQuality: 16, Slot: 3, State: "standby"},
})

data, err := json.Marshal(gatewayInfo)
if err != nil {
    log.Fatal(err)
}
log.Printf("JSON output: %s", data)
// 输出示例:
// [{"imsi":"652025105829193","network":"20801","network_status":"Registered (Roaming)","signal_quality":17,"slot":2,"state":"active"}, ...]

⚠️ 注意事项:

  • 若字段无需出现在 JSON 中,可添加 json:"-" 标签忽略;
  • 若希望零值字段(如空字符串、0)不输出,可追加 ,omitempty(例如 json:"imsi,omitempty");
  • 切片本身无需额外导出——GatewayInfo 是类型别名而非结构体,其序列化行为完全取决于 SpanInfo 字段的导出状态与标签配置。

总结:Go 的 JSON 序列化是“导出即可见”,小写字段等于对 encoding/json 不可见。务必统一使用大写首字母 + json 标签,这是实现可控、可读、符合 API 规范的 JSON 输出的基础实践。