我在构建可以动态使用参数化结构的函数时遇到一些麻烦。因此,我的代码有 20 多个类似的函数,除了基本上使用的一种类型之外。我的大部分经验都是使用 Java,我只是开发基本的通用函数,或者使用普通对象作为函数的参数(以及从那时起的反射)。我需要类似的东西,使用 Go。
我有几种类型,例如:
// The List structs are mostly needed for json marshalling
type OrangeList struct {
Oranges []Orange
}
type BananaList struct {
Bananas []Banana
}
type Orange struct {
Orange_id string
Field_1 int
// The fields are different for different types, I am simplifying the code example
}
type Banana struct {
Banana_id string
Field_1 int
// The fields are different for different types, I am simplifying the code example
}
然后我有函数,基本上针对每种列表类型:
// In the end there are 20+ of these, the only difference is basically in two types!
// This is very un-DRY!
func buildOranges(rows *sqlx.Rows) ([]byte, error) {
oranges := OrangeList{} // This type changes
for rows.Next() {
orange := Orange{} // This type changes
err := rows.StructScan(&orange) // This can handle each case already, could also use reflect myself too
checkError(err, "rows.Scan")
oranges.Oranges = append(oranges.Oranges,orange)
}
checkError(rows.Err(), "rows.Err")
jsontext, err := json.Marshal(oranges)
return jsontext, err
}
是的,我可以更改 sql 库以使用更智能的 ORM 或框架,但这不是重点。我想了解如何构建可以为所有不同类型处理类似函数的通用函数。
我已经做到了这一点,但它仍然无法正常工作(我认为目标不是预期的结构):
func buildWhatever(rows *sqlx.Rows, tgt interface{}) ([]byte, error) {
tgtValueOf := reflect.ValueOf(tgt)
tgtType := tgtValueOf.Type()
targets := reflect.SliceOf(tgtValueOf.Type())
for rows.Next() {
target := reflect.New(tgtType)
err := rows.StructScan(&target) // At this stage target still isn't 1:1 smilar struct so the StructScan fails... It's some perverted "Value" object instead. Meh.
// Removed appending to the list because the solutions for that would be similar
checkError(err, "rows.Scan")
}
checkError(rows.Err(), "rows.Err")
jsontext, err := json.Marshal(targets)
return jsontext, err
}
所以嗯,我需要提供列表类型和普通类型作为参数,然后构建每个类型,并且我的其余逻辑可能很容易修复。
最佳答案
事实证明,有一个 sqlx.StructScan(rows, &destSlice)
函数 可以在给定适当类型的 slice 的情况下执行内部循环。 sqlx
文档引用了反射操作的缓存结果,因此与编写相比,它可能有一些额外的优化。
听起来您实际上要问的直接问题是“如何从我的 reflect.Value
中获取 rows.StructScan
会接受的内容?”直接的答案是reflect.Interface(target)
;它应该返回一个代表 *Orange
的 interface{}
,您可以直接传递给 StructScan
(无需额外的 &
操作需要)。然后,我认为 targets = Reflect.Append(targets, target.Indirect())
会将您的 target
转换为 reflect.Value
代表Orange
并将其附加到 slice 中。 targets.Interface()
应该为您提供一个 interface{}
,表示 []Orange
,json.Marshal
可以理解。我说所有这些“应该”和“我认为”是因为我还没有尝试过那条路线。
一般来说,反射是冗长且缓慢的。有时这是完成某件事的最佳或唯一方法,但通常值得寻找一种方法来在没有它的情况下完成任务。
因此,如果它适用于您的应用,您还可以将 Rows
直接转换为 JSON,而无需通过中间结构。这是一个示例程序(当然需要 sqlite3
),它将 sql.Rows
转换为 map[string]string
,然后转换为 JSON。 (请注意,它不会尝试处理 NULL
、将数字表示为 JSON 数字,或者通常处理任何不适合 map[string]string
的内容。)
package main
import (
_ "code.google.com/p/go-sqlite/go1/sqlite3"
"database/sql"
"encoding/json"
"os"
)
func main() {
db, err := sql.Open("sqlite3", "foo")
if err != nil {
panic(err)
}
tryQuery := func(query string, args ...interface{}) *sql.Rows {
rows, err := db.Query(query, args...)
if err != nil {
panic(err)
}
return rows
}
tryQuery("drop table if exists t")
tryQuery("create table t(i integer, j integer)")
tryQuery("insert into t values(?, ?)", 1, 2)
tryQuery("insert into t values(?, ?)", 3, 1)
// now query and serialize
rows := tryQuery("select * from t")
names, err := rows.Columns()
if err != nil {
panic(err)
}
// vals stores the values from one row
vals := make([]interface{}, 0, len(names))
for _, _ = range names {
vals = append(vals, new(string))
}
// rowMaps stores all rows
rowMaps := make([]map[string]string, 0)
for rows.Next() {
rows.Scan(vals...)
// now make value list into name=>value map
currRow := make(map[string]string)
for i, name := range names {
currRow[name] = *(vals[i].(*string))
}
// accumulating rowMaps is the easy way out
rowMaps = append(rowMaps, currRow)
}
json, err := json.Marshal(rowMaps)
if err != nil {
panic(err)
}
os.Stdout.Write(json)
}
理论上,您可以通过不每次重复使用相同的 rowMap
并使用 json.Encoder
将每行的 JSON 附加到缓冲区来构建它来减少分配。您可以更进一步,根本不使用 rowMap,而只使用名称和值的列表。我应该说我还没有将速度与基于 reflect
的方法进行比较,尽管我知道 reflect
足够慢,如果您可以忍受的话,可能值得对它们进行比较任一策略。
关于reflection - 使用反射和结构来构建通用处理程序函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20294044/