使用带有结构的反射来构建通用处理函数

时间:2013-11-29 22:14:44

标签: reflection go

构建一个可以动态使用参数化结构的函数时遇到了一些麻烦。出于这个原因,我的代码有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类型作为参数,然后构建其中一个,其余的逻辑可能很容易修复。

1 个答案:

答案 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足够慢,如果你能采取任何策略,可能值得比较它们。

相关问题