
本文讲解如何将 SQL 查询(如 SELECT * FROM policy)的多行结果按 name 字段分组,动态构建嵌套结构体(如 { "policy": "xxx", "parameters": [...] }),并正确序列化为符合前端需求的 JSON 格式。
本文讲解如何将 sql 查询(如 `select * from policy`)的多行结果按 `name` 字段分组,动态构建嵌套结构体(如 `{ "policy": "xxx", "parameters": [...] }`),并正确序列化为符合前端需求的 json 格式。
在 Go 中处理关系型数据库查询结果并组织为嵌套 JSON 结构时,核心挑战在于:同一策略名(如 name)可能对应多条记录,需按该字段聚合为顶层对象,再将同名记录归入其 P(即 parameters)切片中。直接逐行扫描赋值到单个 Parameters 实例是可行的,但要升维到 table 结构,关键在于使用 以 Name 为键的 map 进行分组聚合。
首先,修正结构体命名规范(Go 中导出字段首字母需大写)并优化 JSON 标签:
type Table struct { // 首字母大写,导出类型
Policy string `json:"policy"`
P []Parameters `json:"parameters"` // 建议使用语义清晰的字段名
}
type Parameters struct {
PolicyID string `json:"policy_id"`
ClassID string `json:"class_id"`
Name string `json:"name"`
// ... 其他字段
}
接着,在扫描 SQL 结果时,使用 map[string]Table 按 Name 分组累积数据:
// 初始化分组容器:policy name → Table
policyMap := make(map[string]Table)
for rows.Next() {
var p Parameters
if err := rows.Scan(&p.PolicyID, &p.ClassID, &p.Name); err != nil {
log.Printf("scan error: %v", err)
continue // 或根据业务选择中断
}
// 若该 policy name 尚未存在,初始化 Table 实例
if _, exists := policyMap[p.Name]; !exists {
policyMap[p.Name] = Table{
Policy: p.Name,
P: make([]Parameters, 0), // 预分配空切片,避免 nil 引用
}
}
// 将当前参数追加到对应 policy 的参数列表中
policyMap[p.Name].P = append(policyMap[p.Name].P, p)
}
// 关闭 rows(务必在循环后调用)
rows.Close()
// 转换为 JSON 可用的 slice(若需数组而非 map)
var result []Table
for _, t := range policyMap {
result = append(result, t)
}
jsonData, err := json.Marshal(result)
if err != nil {
log.Fatal("JSON marshal failed:", err)
}
fmt.Println(string(jsonData))
注意事项与最佳实践:
- ✅ 字段导出规则:结构体字段必须首字母大写(如
PolicyID)才能被json包序列化; - ✅ 空切片优于 nil:
make([]Parameters, 0)确保append安全执行,避免 panic; - ⚠️ SQL 字段顺序需严格匹配:
rows.Scan()参数顺序必须与SELECT字段顺序一致(建议显式列出字段,避免SELECT *的隐式依赖); - ⚠️ 错误处理不可省略:
rows.Err()应在循环结束后检查,确保所有行已正确读取; - ? 前端兼容性:最终输出为
[]Table(JSON 数组),每个元素形如{"policy":"A","parameters":[{"policy_id":"1","class_id":"01"}]},可直接被 JavaScript 解析。
通过这种“先分组、再聚合”的模式,你既能保持代码清晰性,又能精准生成所需的嵌套 JSON 结构,无需手动构造或多次查询。











