-1

我想用马提尼将 CSV 数据打印到输出中。目前,我一直使用r.JSON(200, somestruct)where ris a render.Renderfrom github.com/martini-contrib

现在我有一片结构,我想将它们打印为 CSV(将单个结构的每个字段字符串化并在一行打印一个结构)。

目前,我这样做:

r.Data(200, []byte("id,Latitude,Longitude\n"))
for _, packet := range tour.Packets {
    r.Data(200, []byte(strconv.FormatInt(packet.Id, 10)+","+strconv.FormatFloat(packet.Latitude, 'f', 6, 64)+","+strconv.FormatFloat(packet.Longitude, 'f', 6, 64)+"\n"))
}

但我不喜欢我这样做的方式,原因如下:

  • 它是直接下载的,不会打印到屏幕上。
  • 我明白了http: multiple response.WriteHeader calls
  • 我不希望手动进行此操作(结构有更多字段,但所有字段都是ìnt64,float64time.Time.

如何以更简单的方式实现 CSV 导出选项?

4

1 回答 1

-1

使用标准库。没有反射就没有通用的解决方案,但是您可以将其简化。

func handler(rw http.ResponseWriter) {
    rw.Header().Add("Content-Type", "text/csv")
    wr := csv.NewWriter(rw)
    err := wr.Write([]string{"id", "Latitude", "Longitude"})
    if err != nil {
        ...
    }
    for _, packet := range tour.Packets {
        err := wr.Write([]string{
            strconv.FormatInt(packet.Id, 10),
            strconv.FormatFloat(packet.Latitude, 'f', 6, 64),
            strconv.FormatFloat(packet.Longitude, 'f', 6, 64),
        })
        if err != nil {
            ...
        }
    }
}

如果您需要任何结构的通用解决方案,则需要反射。见这里

// structToStringSlice takes a struct value and
// creates a string slice of all the values in that struct
func structToStringSlice(i interface{}) []string {
    v := reflect.ValueOf(i)
    n := v.NumField()
    out := make([]string, n)
    for i := 0; i < n; i++ {
        field := v.Field(i)
        switch field.Kind() {
        case reflect.String:
            out[i] = field.String()
        case reflect.Int:
            out[i] = strconv.FormatInt(field.Int(), 10)
        // add cases here to support more field types.
        }
    }
    return out
}

// writeToCSV prints a slice of structs as csv to a writer
func writeToCSV(w io.Writer, i interface{}) {
    wr := csv.NewWriter(w)
    v := reflect.ValueOf(i)

    // Get slice's element type (some unknown struct type)
    typ := v.Type().Elem()
    numFields := typ.NumField()
    fieldSet := make([]string, numFields)
    for i := 0; i < numFields; i++ {
        fieldSet[i] = typ.Field(i).Name
    }
    // Write header row
    wr.Write(fieldSet)

    // Write data rows
    sliceLen := v.Len()
    for i := 0; i < sliceLen; i++ {
        wr.Write(structToStringSlice(v.Index(i).Interface()))
    }
    wr.Flush()
}

那么你的例子就是:

func handler(rw http.ResponseWriter) {
    ....
    writeToCSV(rw, tour.Packets)
}

我编写的函数仅适用于 int 或 string 字段。您可以通过在 structToStringSlice 中将 case 添加到 switch 来轻松地将其扩展到更多类型。请参阅此处以获取有关其他文档的反射文档Kinds

于 2015-03-21T18:56:53.067 回答