
本文讲解如何将 SQL 查询(如 SELECT * FROM policy)的多行结果按字段(如 name)分组,动态构建带嵌套切片的 Go 结构体,并正确序列化为符合前端预期的 JSON 格式。
本文讲解如何将 sql 查询(如 `select * from policy`)的多行结果按字段(如 `name`)分组,动态构建带嵌套切片的 go 结构体,并正确序列化为符合前端预期的 json 格式。
在 Go Web 开发中,常需将数据库查询结果结构化为嵌套 JSON 响应(例如按策略名称聚合参数列表)。但直接将单行扫描结果赋值给顶层结构体容易出错——关键在于理解:table 是聚合单元,而 Parameters 是明细项;一个 table 对应多个 Parameters,需以 Name 为键进行分组聚合,而非逐行构造独立 table 实例。
以下是推荐实现方案:
✅ 正确结构定义与初始化
首先修正结构体命名规范(首字母大写以导出)并明确字段语义:
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"`
}
✅ 按 Name 分组聚合数据
使用 map[string]Table 作为临时容器,以 p.Name 为键自动归并同名策略的所有参数:
详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。
ts := 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 名称首次出现,则初始化空切片
if _, exists := ts[p.Name]; !exists {
ts[p.Name] = Table{
Policy: p.Name,
P: make([]Parameters, 0),
}
}
// 追加当前参数到对应策略的切片中
ts[p.Name].P = append(ts[p.Name].P, p)
}
✅ 转换为 JSON 响应
最终将 map 转为 slice(若需数组格式)或直接编码:
// 方案1:输出为 JSON 对象(key 为 policy name)
jsonData, _ := json.Marshal(ts) // 输出形如 {"policyName1": {...}, "policyName2": {...}}
// 方案2:输出为 JSON 数组(更常见于前端消费)
var result []Table
for _, t := range ts {
result = append(result, t)
}
jsonData, _ := json.Marshal(result) // 输出形如 [{"policy":"...", "parameters":[...]}, ...]
⚠️ 注意事项
-
SQL 字段顺序必须严格匹配
Scan()参数顺序,建议显式指定列名(如SELECT policy_id, class_id, name FROM policy),避免因表结构变更导致静默错误。 -
rows.Scan()的地址符&不可遗漏,否则会 panic。 - 若
name可能为空或重复逻辑需特殊处理,应在Scan后添加校验(如if p.Name == "" { continue })。 - 生产环境务必用
defer rows.Close()和完整的错误处理替代示例中的简化逻辑。
通过该模式,你既能保持结构体语义清晰,又能精准生成类似 { "policy": "policyName1", "parameters": [ { "policy_id": "1", "class_id": "01" }, ... ] } 的标准 JSON,无缝对接前端渲染逻辑。










