Reflection 使用反射和结构构建通用处理程序函数
我在构建一个可以动态使用参数化结构的函数时遇到了一些困难。出于这个原因,我的代码有20多个类似的函数,除了基本上使用的一种类型。我的大部分经验都是使用Java的,我只会开发基本的泛型函数,或者使用普通对象作为函数的参数(以及从那时起的反射)。我需要类似的东西,用围棋 我有几种类型,如:Reflection 使用反射和结构构建通用处理程序函数,reflection,go,Reflection,Go,我在构建一个可以动态使用参数化结构的函数时遇到了一些困难。出于这个原因,我的代码有20多个类似的函数,除了基本上使用的一种类型。我的大部分经验都是使用Java的,我只会开发基本的泛型函数,或者使用普通对象作为函数的参数(以及从那时起的反射)。我需要类似的东西,用围棋 我有几种类型,如: // The List structs are mostly needed for json marshalling type OrangeList struct { Oranges []Orange }
// 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或框架,但这不是重点。我想学习如何构建泛型函数,以处理我所有不同类型的类似函数
我已经做到了这一点,但它仍然不能正常工作(我认为目标不是预期的结构):
因此,嗯,我需要给出列表类型和香草类型作为参数,然后分别构建其中一个,我的其余逻辑可能很容易修复。结果是有一个
sqlx.StructScan(rows,&destSlice)
函数,它将在给定适当类型的切片的情况下执行内部循环。sqlx
文档引用了缓存反射操作的结果,因此与编写反射操作相比,它可能有一些额外的优化
听起来你实际上要问的直接问题是“我如何从我的reflect.Value
中得到rows.StructScan
可以接受的东西?”而直接的答案是reflect.Interface(target)
;它应该返回一个接口{}
,表示一个*橙色
,您可以直接传递到StructScan
(无需额外的&
操作)。然后,我认为targets=reflect.Append(targets,target.Indirect())
会将您的target
转换为一个reflect.Value
表示一个Orange
并将其附加到切片中targets.Interface()
应该为您提供一个Interface{}
来表示json.Marshal
能够理解的Orange
。我说所有这些“应该”和“我认为”是因为我没有尝试过这条路线
一般来说,反射是冗长而缓慢的。有时候,这是完成某件事的最好或唯一的方法,但当你可以的时候,寻找一种不用它完成任务的方法往往是值得的
因此,如果它在你的应用程序中起作用,你也可以直接将行
转换为JSON,而无需经过中间结构。下面是一个示例程序(当然需要sqlite3
),它将sql.Rows
转换为map[string]string
,然后转换为JSON。(注意,它不会尝试处理NULL
,不会将数字表示为JSON数字,也不会处理任何不适合map[string]字符串的内容
)
理论上,通过每次不重用相同的
rowMap
,并使用json.Encoder
将每一行的json追加到缓冲区,您可以构建此功能以减少分配。您可以更进一步,根本不使用行映射
,只使用名称和值的列表。我应该说,我没有将速度与基于<代码>反射代码>的方法进行比较,尽管我知道<代码>反射代码>足够慢,如果你能忍受这两种策略,可能值得进行比较。有趣的问题,有多种答案:1)如何修复<代码>反射代码>代码,2)如何将行以更干燥的方式保存到不同类型的对象(即使不直接调用reflect
),3)如何将行
序列化为JSON(有或没有保存到中间的对象)。对于第一个问题,我认为将&target
替换为target.Interface()
将为您提供一个接口{}
,表示*橙色
,我认为结构扫描
可以接受。
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
}
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)
}