
本文详解 Go 语言中从 MySQL 读取数据后,如何定义可导出字段的结构体、安全扫描查询结果,并正确使用 json.Marshal 转换为 []byte——解决因字段未导出导致序列化为空对象 {} 的常见问题。
本文详解 go 语言中从 mysql 读取数据后,如何定义可导出字段的结构体、安全扫描查询结果,并正确使用 json.marshal 转换为 []byte——解决因字段未导出导致序列化为空对象 `{}` 的常见问题。
在 Go 语言中,json.Marshal 只能序列化导出(exported)字段,即首字母大写的字段;而小写开头的字段(如 id、country_code)属于未导出字段,在 JSON 序列化时会被忽略,最终输出空对象 {}。这正是你看到 [{}, {}] 和字节流 [91 123 125 44 123 125 93](对应 [{},{}])的根本原因。
✅ 正确做法:使用导出字段 + 匹配 Scan 参数
首先,修改 Country 结构体,将所有字段名改为大驼峰命名,并保持 JSON 标签语义清晰:
type Country struct {
ID int `json:"id"`
CountryCode string `json:"country_code"`
CountryName string `json:"country_name"`
PhoneCode string `json:"phone_code"`
Icon string `json:"icon"`
}
? 建议 JSON 标签使用简洁键名(如
"id"而非"Country.id"),更符合 REST/消息协议惯例,也便于下游解析。
接着,同步更新 GetAllCountry() 函数中的 rows.Scan 参数,确保与结构体字段一一对应:
func GetAllCountry() []*Country {
dbConnection := db.ConnectMySQL()
defer dbConnection.Close() // 避免连接泄漏(建议补充)
rows, err := dbConnection.Query("SELECT id, country_code, country_name, phone_code, icon FROM country;")
if err != nil {
log.Fatal("MySQL query failed:", err)
}
defer rows.Close()
var countries []*Country
for rows.Next() {
country := &Country{} // 每次循环创建新实例,避免指针复用
if err := rows.Scan(&country.ID, &country.CountryCode, &country.CountryName, &country.PhoneCode, &country.Icon); err != nil {
log.Fatal("Scan failed:", err)
}
countries = append(countries, country)
}
if err := rows.Err(); err != nil { // 检查迭代过程中的潜在错误
log.Fatal("Rows iteration error:", err)
}
return countries
}
⚠️ 关键修正点:
- 使用
country := &Country{}替代new(Country)并重复复用同一地址,否则所有切片元素指向同一个内存地址,导致最后只保留最后一次扫描的数据; - 添加
rows.Err()检查,捕获Next()迭代末尾可能发生的错误(如网络中断、类型不匹配等); - 补充
dbConnection.Close()(或通过defer管理),防止连接池耗尽。
? 序列化为 []byte:健壮封装示例
GetCountry 函数可进一步优化为带错误返回的版本,提升可观测性与容错能力:
func GetCountry() ([]byte, error) {
countries := countryModel.GetAllCountry()
log.Printf("Fetched %d countries", len(countries))
data, err := json.Marshal(countries)
if err != nil {
logger.Error(err, "JSON marshal failed in GetCountry")
return nil, fmt.Errorf("failed to marshal countries: %w", err)
}
log.Debugf("Serialized JSON bytes: %s", string(data))
return data, nil
}
调用方示例:
if payload, err := GetCountry(); err == nil {
// 发送至 MessageBroker(如 Kafka/RabbitMQ)
broker.Publish("country.update", payload)
} else {
log.Error("Failed to generate country payload:", err)
}
? 总结与最佳实践
- ✅ 结构体字段必须首字母大写:Go 的反射机制要求 JSON 序列化/反序列化仅对导出字段生效;
- ✅ 每次循环创建独立结构体指针:避免切片中所有元素引用同一地址;
- ✅ 始终检查
rows.Err():rows.Next()不会报告扫描后的错误,需显式校验; - ✅ 使用
defer dbConnection.Close()或连接池管理:避免资源泄漏; - ✅ JSON 标签推荐语义化简写:如
"id"而非"Country.id",提升 API 兼容性与可读性。
遵循以上规范,即可稳定实现「MySQL → Struct → []byte」的端到端数据流转,彻底规避空对象问题。











