
本文讲解如何通过定义嵌套结构体(如 RatingPK 包裹 Rating),使 Go 的 JSON 序列化输出符合 { "ratingsPK": { ... } } 的嵌套格式,而非扁平数组。
本文讲解如何通过定义嵌套结构体(如 `ratingpk` 包裹 `rating`),使 go 的 json 序列化输出符合 `{ "ratingspk": { ... } }` 的嵌套格式,而非扁平数组。
在 Go 中,若希望将一组原始数据对象(如 Rating)封装进一个“容器”结构体(如 RatingPK),并最终以 {"ratingsPK": {...}} 的 JSON 形式输出,关键在于正确设计结构体字段命名、类型及序列化行为,而非仅靠运行时赋值逻辑。
首先,需修正原始代码中的两个核心问题:
-
返回类型不匹配:原函数
searchUserRatings声明返回[]Rating,但实际想构造的是[]RatingPK—— 必须同步更新返回类型; -
JSON 字段名未显式声明:Go 的
json包默认使用字段名小写形式(如ratingsPK),但 JSON 规范要求首字母小写的字段无法被外部系统识别为合法键名(如"ratingsPK")。需通过jsontag 显式指定导出字段名。
✅ 正确做法如下:
// Rating 表示数据库中的一条评分记录
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"`
}
// RatingPK 是一个包装器,用于将单个 Rating 嵌入到名为 "ratingsPK" 的 JSON 字段中
type RatingPK struct {
RatingsPK Rating `json:"ratingsPK"` // 注意:字段名首字母大写(导出),tag 指定 JSON 键为 "ratingsPK"
}? 关键点:
RatingsPK字段必须是导出字段(首字母大写),且通过json:"ratingsPK"显式控制序列化后的键名;内部嵌套的Rating类型自动继承其自身的jsontag。
接着,更新查询函数,直接构建 []RatingPK 并返回:
使用 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 results []RatingPK
for rows.Next() {
var r Rating
// 注意字段顺序需与 SELECT * 严格一致;推荐显式列出字段以提高可维护性
if err := rows.Scan(&r.MovieId, &r.UserId, &r.RatingId, &r.Rating, &r.Timestamp); err != nil {
log.Printf("Scan error: %v", err)
continue // 跳过单条错误,不中断整个查询
}
results = append(results, RatingPK{RatingsPK: r})
}
return results
}调用示例与 JSON 输出验证:
func main() {
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"
}
}
]⚠️ 注意事项:
- 避免使用指针接收器
*RatingPK配合Init()方法初始化——对于简单嵌套,直接字面量赋值(RatingPK{RatingsPK: r})更清晰、零分配、无副作用; -
defer rows.Close()必须在rows创建后尽早声明(通常紧随db.Query后),否则可能因提前return导致资源泄漏; - 若
time.Time字段在数据库中可能为空,请使用sql.NullTime并自定义json.Marshaler实现安全序列化; -
SELECT *在生产环境不推荐,应明确列出所需字段并保持与Scan顺序一致,增强健壮性。
总结:Go 中实现“对象嵌套对象”的 JSON 输出,本质是结构体组合 + JSON tag 精确控制。无需复杂逻辑,只需合理建模、导出字段、正确序列化,即可自然达成嵌套语义。

















