
本文详解如何在 go 中编写通用函数,执行任意 select 查询并将结果以键值对(而非纯数组)形式序列化为标准 json,解决字段名丢失问题。
本文详解如何在 go 中编写通用函数,执行任意 select 查询并将结果以键值对(而非纯数组)形式序列化为标准 json,解决字段名丢失问题。
在 Go 中使用 database/sql 执行动态 SELECT 查询并生成符合预期的 JSON 输出(如 {"Data":[{"id":1,"name":"abc","age":40}]}),关键在于将每行查询结果从 []interface{} 数组映射为 map[string]interface{},从而保留列名与值的对应关系。原始代码中直接将 values 切片追加到 Data,导致 JSON 序列化为二维数组([[1,"abc",40]]),而非对象数组([{"id":1,"name":"abc","age":40}])。
以下是优化后的完整实现,已整合错误处理、资源安全释放及类型兼容性考量:
import (
"database/sql"
"encoding/json"
"log"
)
func executeSQL(queryStr string) []byte {
connString := createConnectString()
conn, err := sql.Open("mssql", connString)
if err != nil {
log.Fatal("Error while opening database connection:", err)
}
defer func() {
if err := conn.Close(); err != nil {
log.Printf("Warning: failed to close database connection: %v", err)
}
}()
rows, err := conn.Query(queryStr)
if err != nil {
log.Fatal("Query execution failed:", err)
}
defer func() {
if err := rows.Close(); err != nil {
log.Printf("Warning: failed to close rows: %v", err)
}
}()
// 获取列名(顺序与 SELECT 字段严格一致)
columns, err := rows.Columns()
if err != nil {
log.Fatal("Failed to retrieve column names:", err)
}
var result struct {
Data []map[string]interface{} `json:"Data"`
}
// 逐行扫描
for rows.Next() {
// 为当前行分配值容器
values := make([]interface{}, len(columns))
valuePtrs := make([]interface{}, len(columns))
for i := range values {
valuePtrs[i] = &values[i]
}
if err := rows.Scan(valuePtrs...); err != nil {
log.Fatal("Row scan failed:", err)
}
// 构建 map[string]interface{},绑定列名与值
rowMap := make(map[string]interface{})
for i, col := range columns {
// 处理 nil 值:sql.Null* 类型需解包,此处简化处理;生产环境建议用类型断言或 scanner
if val := values[i]; val != nil {
rowMap[col] = val
} else {
rowMap[col] = nil
}
}
result.Data = append(result.Data, rowMap)
}
// 检查 rows.Next() 循环后是否发生错误(如网络中断)
if err := rows.Err(); err != nil {
log.Fatal("Error during row iteration:", err)
}
jsonBytes, err := json.Marshal(result)
if err != nil {
log.Fatal("JSON marshaling failed:", err)
}
return jsonBytes
}✅ 关键改进说明:
使用约定式提交(Conventional Commits)生成标准化的 Git 提交信息。适用于以下场景: (1) 暂存更改时需要提交信息; (2) 审查 diff 时希望获得结构化摘要; (3) 编写 PR 描述或变更日志(changelog)条目; (4) 用户明确要求“为本次更改编写提交信息”。 该功能会分析已暂存/未暂存的 diff,并生成按类型(type)划分范围的提交信息。
- 使用
map[string]interface{}替代[]interface{}存储每行数据,确保 JSON 输出含字段名; - 显式调用
rows.Columns()获取列名切片(注意:rows.Columns()返回的是[]string,非*[]string); - 添加
rows.Err()检查,捕获迭代过程中的潜在错误(如连接中断); -
defer conn.Close()和defer rows.Close()改为带错误日志的匿名函数,避免静默失败; - 对
nil值显式赋值nil,保证 JSON 中对应字段为null(如{"age":null}),提升健壮性。
⚠️ 注意事项:
- 此方案适用于只读查询,不处理
INSERT/UPDATE/DELETE; - 若需支持复杂类型(如
time.Time,sql.NullString),应在构建rowMap时做类型断言与转换; - 生产环境建议使用结构体或 ORM(如
sqlx)提升类型安全性与性能; - 动态 SQL 存在注入风险,请确保
queryStr来源可信,或改用参数化查询(但SELECT字段名无法参数化,需白名单校验)。
该实现简洁、可靠,满足“任意 SELECT 查询 → 标准 JSON 对象数组”的核心需求,是 Go 数据访问层中轻量级 JSON API 开发的典型范式。

















