
本文详解如何在 Go 中正确将数据库查询结果序列化为符合 RESTful API 规范的 JSON 数据,重点解决因结构体字段未导出导致 json.Marshal 返回空对象数组的常见问题,并提供完整可运行示例。
本文详解如何在 go 中正确将数据库查询结果序列化为符合 restful api 规范的 json 数据,重点解决因结构体字段未导出导致 `json.marshal` 返回空对象数组的常见问题,并提供完整可运行示例。
在 Go 语言中,将 SQL 查询结果转换为标准 JSON 响应是构建 Web API 的基础能力。但新手常遇到一个“静默失败”:控制台能正常打印字段值,json.Marshal 却输出 [{},{}] —— 这几乎总是由 结构体字段未导出(即小写首字母) 导致的。Go 的 encoding/json 包仅能序列化首字母大写的导出字段,小写字段(如 name、created_by)会被忽略,最终生成空对象。
✅ 正确做法:导出字段 + 显式 JSON 标签
需将结构体字段名改为大写首字母,并通过 json tag 精确控制输出键名:
type Post struct {
Title string `json:"title"` // 导出字段,映射为 "title"
CreatedBy string `json:"created_by"` // 导出字段,映射为 "created_by"
CreatedAt time.Time `json:"created_at"` // 导出字段,映射为 "created_at"
}
// 封装顶层响应结构(符合 RESTful API 最佳实践)
type PostsResponse struct {
Posts []Post `json:"posts"`
}
✅ 完整可运行示例(修正版 example.go)
package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
type Post struct {
Title string `json:"title"`
CreatedBy string `json:"created_by"`
CreatedAt time.Time `json:"created_at"`
}
type PostsResponse struct {
Posts []Post `json:"posts"`
}
func main() {
db, err := sql.Open("mysql", "root:1234@/blog?parseTime=true")
if err != nil {
log.Fatal("数据库连接失败:", err)
}
defer db.Close()
if err = db.Ping(); err != nil {
log.Fatal("数据库连通性检查失败:", err)
}
rows, err := db.Query("SELECT title, created_by, created_at FROM post")
if err != nil {
log.Fatal("查询执行失败:", err)
}
defer rows.Close()
var posts []Post
for rows.Next() {
var p Post
if err := rows.Scan(&p.Title, &p.CreatedBy, &p.CreatedAt); err != nil {
log.Fatal("扫描行失败:", err)
}
posts = append(posts, p)
}
if err = rows.Err(); err != nil {
log.Fatal("遍历结果集时出错:", err)
}
// 构建符合 API 规范的响应体
response := PostsResponse{Posts: posts}
jsonData, err := json.MarshalIndent(response, "", " ")
if err != nil {
log.Fatal("JSON 序列化失败:", err)
}
fmt.Println(string(jsonData))
}
预期输出:
{
"posts": [
{
"title": "First title",
"created_by": "User1",
"created_at": "2026-05-15T14:22:33Z"
},
{
"title": "My second post",
"created_by": "User1",
"created_at": "2026-05-16T09:08:12Z"
}
]
}
⚠️ 关键注意事项
- 字段必须导出:Go 中只有首字母大写的字段(如 Title)才是导出的,json 包才能访问;title 是私有字段,永远无法被序列化。
- 使用 json.MarshalIndent:便于调试和阅读,生产环境可用 json.Marshal 提升性能。
- 错误处理不可省略:rows.Err() 必须检查,否则可能遗漏 Scan 后的潜在错误(如类型不匹配)。
- 避免指针切片陷阱:不要用 []*Post 存储地址(除非明确需要),易引发内存或空指针问题;直接追加值类型更安全。
- 扩展建议:在真实 HTTP 服务中,应结合 net/http 返回 200 OK 及 Content-Type: application/json 头,并对空结果、分页、错误码做统一封装。
掌握这一基础模式后,你即可无缝对接前端框架(如 React/Vue)、Bootstrap Table 或任何消费 JSON 的客户端,真正实现“数据库 → 结构化 API 响应”的端到端交付。











