
本文讲解如何通过定义嵌套结构体(如 RatingPK 包裹 Rating),配合正确类型声明与 JSON 序列化,将数据库查询结果封装为带外层字段名的嵌套 JSON 数组。
本文讲解如何通过定义嵌套结构体(如 `ratingpk` 包裹 `rating`),配合正确类型声明与 json 序列化,将数据库查询结果封装为带外层字段名的嵌套 json 数组。
在 Go 语言中,“把一个对象放进另一个对象”本质上是通过结构体嵌套(embedding 或组合) 实现的。你希望将每个 Rating 实例包裹进一个名为 ratingsPK 的字段中,并最终生成形如 {"ratingsPK": {...}} 的 JSON 输出——这并非运行时动态“赋值”,而是依赖 Go 结构体字段命名 + JSON 标签(json tag) 的静态序列化机制。
✅ 正确做法:定义嵌套结构体并返回其切片
首先,确保你的嵌套结构体 RatingPK 定义清晰,并利用 json tag 显式控制输出键名:
type Rating struct {
MovieId int `json:"movieId"`
UserId int `json:"userId"`
RatingId int `json:"rating_id"`
Rating int `json:"rating"`
Timestamp time.Time `json:"timestamp"`
}
type RatingPK struct {
RatingsPK Rating `json:"ratingsPK"` // 注意:字段名首字母大写(导出),且 tag 指定为 "ratingsPK"
}⚠️ 关键点:
- 字段
RatingsPK必须是导出字段(首字母大写),否则json.Marshal()无法访问;json:"ratingsPK"控制序列化后的 JSON 键名,大小写和下划线需与目标一致;- 不需要手动
Init()方法——Go 中直接赋值更简洁、高效。
接着,修正 searchUserRatings 函数,返回 []RatingPK 类型(而非 []Rating),并在循环中构造嵌套实例:
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
func searchUserRatings(id string) []RatingPK {
rows, err := db.Query("SELECT * FROM ratings WHERE userId = ?", id)
if err != nil {
log.Printf("DB query error: %v", err)
return nil
}
defer rows.Close() // 建议移至函数开头后立即 defer,避免遗漏
var ratings []RatingPK
for rows.Next() {
var rate Rating
// 注意:Scan 字段顺序必须与 SELECT 列顺序严格一致
err := rows.Scan(&rate.MovieId, &rate.UserId, &rate.RatingId, &rate.Rating, &rate.Timestamp)
if err != nil {
log.Printf("Scan error: %v", err)
continue // 跳过单条错误,不中断整个查询
}
ratings = append(ratings, RatingPK{RatingsPK: rate})
}
return ratings
}? 使用示例:生成目标 JSON
调用该函数后,使用 json.MarshalIndent 可获得你期望的格式:
ratings := searchUserRatings("2")
data, _ := json.MarshalIndent(ratings, "", " ")
fmt.Println(string(data))输出即为:
[
{
"ratingsPK": {
"movieId": 93,
"userId": 2,
"rating_id": 593,
"rating": 3,
"timestamp": "0001-01-01T00:00:00Z"
}
},
{
"ratingsPK": {
"movieId": 94,
"userId": 2,
"rating_id": 616,
"rating": 3,
"timestamp": "0001-01-01T00:00:00Z"
}
}
]? 补充说明与最佳实践
-
无需
Init()方法:Go 推崇组合与直接初始化,RatingPK{RatingsPK: rate}更符合惯用法,也利于编译器优化; -
defer rows.Close()应尽早调用:放在db.Query后立刻defer,确保即使发生 panic 也能释放资源; -
错误处理建议增强:生产环境应区分
rows.Err()(迭代结束后的潜在错误)与rows.Scan()错误,并记录日志; -
时间字段注意:若数据库中
timestamp为NULL或零值,time.Time会默认为0001-01-01T00:00:00Z,可考虑用*time.Time或自定义NullTime类型处理空值。
通过合理设计结构体与 JSON tag,你就能自然、安全、高效地实现“对象包裹对象”的序列化需求,无需反射或运行时拼接。

















