构建一个可以动态使用参数化结构的函数时遇到了一些麻烦。出于这个原因,我的代码有20多个类似的功能,除了基本上是一种使用的类型。我的大多数经验都是用Java编写的,我只是开发基本的泛型函数,或者使用普通的Object作为函数的参数(以及从那一点开始的反射)。我需要类似的东西,使用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
}
嗯,我需要给出列表类型和vanilla类型作为参数,然后构建其中一个,其余的逻辑可能很容易修复。
答案 0 :(得分:2)
原来有一个sqlx.StructScan(rows, &destSlice)
函数,它将执行你的内部循环,给定一个合适类型的片段。 sqlx
文档引用了反射操作的缓存结果,因此与编写反射操作相比,它可能会有一些额外的优化。
听起来你实际问的直接问题是“如何从reflect.Value
rows.StructScan
接受的reflect.Interface(target)
中获取某些内容?”直接答案是interface{}
;它应该返回代表*Orange
的{{1}},您可以直接转到StructScan
(无需额外的&
操作)。然后,我认为targets = reflect.Append(targets, target.Indirect())
会将您的target
转换为代表reflect.Value
的{{1}}并将其附加到切片。 Orange
会为您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
足够慢,如果你能采取任何策略,可能值得比较它们。