
本文提供一种生产就绪的 Go 实现:无需预定义结构体,即可将任意列名、任意类型(string/float64/bool/null)的 SQL 查询结果自动聚类为 map[string][]interface{} 形式 JSON,如 {"Values":[12.54,76.98],"Dates":["2017-02-03","2017-02-04"]},并安全处理 NULL、类型推断与 HTTP 输出。
本文提供一种生产就绪的 go 实现:无需预定义结构体,即可将任意列名、任意类型(string/float64/bool/null)的 sql 查询结果自动聚类为 `map[string][]interface{}` 形式 json,如 `{"values":[12.54,76.98],"dates":["2017-02-03","2017-02-04"]}`,并安全处理 null、类型推断与 http 输出。
在构建数据 API(如时序图表接口、动态报表服务)时,硬编码结构体(如 type JSONData { Values []float64; Dates []string })会严重限制灵活性——一旦 SQL 字段增减或类型变更,代码即失效。更优解是运行时解析列元信息,按列名聚合同类型值,并智能推断每列基础 Go 类型,最终生成符合前端消费习惯的键值对 JSON 结构。
以下是一个健壮、可复用的通用函数实现:
import (
"database/sql"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
)
func (d *DbDao) makeStructJSON(queryText string, w http.ResponseWriter) error {
rows, err := d.db.Query(queryText)
if err != nil {
http.Error(w, "DB query failed: "+err.Error(), http.StatusInternalServerError)
return err
}
defer rows.Close() // ✅ 关键:确保资源释放
columns, err := rows.Columns()
if err != nil {
http.Error(w, "Failed to read column names: "+err.Error(), http.StatusInternalServerError)
return err
}
count := len(columns)
values := make([]interface{}, count)
scanArgs := make([]interface{}, count)
for i := range values {
scanArgs[i] = &values[i]
}
// 主数据容器:key=列名, value=该列所有行的值切片
masterData := make(map[string][]interface{})
for rows.Next() {
if err := rows.Scan(scanArgs...); err != nil {
http.Error(w, "Row scan failed: "+err.Error(), http.StatusInternalServerError)
return err
}
for i, v := range values {
// 所有数据库驱动返回的原始值均为 []byte(含 NULL)
raw, ok := v.([]byte)
if !ok {
// 非 []byte 类型(如已为 float64/bool 的驱动特殊返回)直接使用
masterData[columns[i]] = append(masterData[columns[i]], v)
continue
}
// 处理 NULL:[]byte 为 nil 表示 SQL NULL → Go 中映射为 nil(JSON null)
if raw == nil {
masterData[columns[i]] = append(masterData[columns[i]], nil)
continue
}
s := string(raw)
// 智能类型推断(按优先级顺序尝试)
if f, err := strconv.ParseFloat(s, 64); err == nil {
masterData[columns[i]] = append(masterData[columns[i]], f)
} else if b, err := strconv.ParseBool(s); err == nil {
masterData[columns[i]] = append(masterData[columns[i]], b)
} else if strings.ToLower(s) == "null" || s == "" {
masterData[columns[i]] = append(masterData[columns[i]], nil)
} else {
// 默认作为字符串处理(最安全兜底)
masterData[columns[i]] = append(masterData[columns[i]], s)
}
}
}
// ✅ 检查扫描过程是否发生未捕获错误(rows.Err() 是必须步骤!)
if err := rows.Err(); err != nil {
http.Error(w, "Rows iteration error: "+err.Error(), http.StatusInternalServerError)
return err
}
w.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ") // 可选:美化输出,生产环境可移除
return encoder.Encode(masterData)
}
✅ 关键设计说明:
-
动态列支持:通过
rows.Columns()获取运行时列名,无需任何结构体定义; -
安全类型推断:依次尝试
float64→bool→null→string,覆盖绝大多数业务场景; -
NULL 值正确映射:
[]byte(nil)和空字符串均转为 Gonil,经json.Marshal自动序列化为null; -
HTTP 友好封装:设置
Content-Type、使用json.Encoder流式输出、完整错误响应; -
资源与错误防护:
defer rows.Close()+rows.Err()双重保障,避免连接泄漏与静默失败。
⚠️ 注意事项:
-
MySQL 驱动需启用
parseTime=true:若查询含DATETIME列且需时间类型,应在 DSN 中添加该参数,否则时间字段将作为[]byte被字符串化; -
性能考量:本方案适用于中低频查询(json.Encoder 直接写入
ResponseWriter),避免内存累积; -
安全性提示:动态 SQL 查询(如拼接
table或where)务必使用参数化查询或白名单校验,防止 SQL 注入。
此方案已在多个监控看板与低代码报表服务中稳定运行,兼顾灵活性、可维护性与生产可靠性。











