
本文讲解 Go 语言中为何 json.Marshal 对非导出字段(小写首字母)返回空对象,以及如何正确定义结构体以支持 JSON 序列化,附带完整可运行示例和最佳实践。
本文讲解 go 语言中为何 `json.marshal` 对非导出字段(小写首字母)返回空对象,以及如何正确定义结构体以支持 json 序列化,附带完整可运行示例和最佳实践。
在 Go 中,只有首字母大写的字段(即导出字段)才能被外部包(如 encoding/json)访问和序列化。这是 Go 的基础可见性规则:小写开头的标识符(如 name、created_by)属于包级私有,json 包无法反射读取其值,因此 json.Marshal 输出的始终是空对象 {}。
你代码中的 Post 结构体字段全部为小写开头:
type Post struct {
name string `json:"name"` // ❌ 非导出,json 包不可见
created_by string `json:"created_by"`
created_at time.Time `json:"created_at"`
}即使你显式添加了 json 标签,也无法绕过导出规则——标签仅控制如何序列化已可见的字段,而非“让不可见字段变得可见”。
✅ 正确写法:将字段名首字母大写,并保持 json 标签指定对外键名:
type Post struct {
Name string `json:"name"` // ✅ 导出字段,可被 json 包读取
CreatedBy string `json:"created_by"` // ✅ 驼峰命名 + 自定义 JSON 键
CreatedAt time.Time `json:"created_at"` // ✅ time.Time 可直接序列化为 ISO8601 字符串
}
type Posts []Post同时,建议优化主逻辑以增强健壮性(如错误处理、资源释放):
func main() {
db, err := sql.Open("mysql", "root:1234@/blog?parseTime=true")
if err != nil {
panic(err)
}
defer db.Close()
if err = db.Ping(); err != nil {
panic(err)
}
rows, err := db.Query("SELECT title, created_by, created_at FROM post")
if err != nil {
panic(err)
}
defer rows.Close()
var posts Posts
for rows.Next() {
var p Post
// 注意:SQL 列名需与结构体字段顺序/类型严格匹配
if err := rows.Scan(&p.Name, &p.CreatedBy, &p.CreatedAt); err != nil {
panic(err)
}
posts = append(posts, p)
}
// 检查 rows.Next() 循环后是否有扫描错误(如类型不匹配)
if err = rows.Err(); err != nil {
panic(err)
}
result := map[string]Posts{"posts": posts} // 包装为 {"posts": [...]} 结构
data, err := json.MarshalIndent(result, "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(data))
}? 补充说明:
- json.MarshalIndent 提供格式化输出,便于调试;
- 使用 map[string]Posts 封装,精准匹配你期望的响应结构 {"posts": [...]};
- 始终调用 rows.Err() 检查迭代结束后的潜在错误(如 Scan 类型不匹配未被捕获);
- 若 SQL 列名与字段名不一致(如数据库用 title,结构体用 Name),Scan 依赖位置顺序而非名称,确保 SELECT title, created_by, created_at 与 &p.Name, &p.CreatedBy, &p.CreatedAt 一一对应。
⚠️ 注意事项:
- 不要尝试用 *Post 指针切片(如 []*Post)来“绕过”导出限制——问题根源是字段不可见,而非指针与否;
- time.Time 默认序列化为 RFC3339 格式(如 "2026-05-16T22:02:00+08:00"),符合 API 最佳实践;如需自定义格式,可实现 MarshalJSON() 方法;
- 生产环境应使用连接池、上下文超时、SQL 参数化防注入,本例聚焦核心序列化问题。
掌握导出规则是 Go 开发的基石。记住口诀:“小写私有,大写公开;JSON 只认大写字段” —— 从此告别 [{},{},{}]。


















