【问题标题】:How can I output CSV with martini?如何用马提尼酒输出 CSV?
【发布时间】:2015-03-21 13:15:58
【问题描述】:

我想用 martini 将 CSV 数据打印到输出。目前,我一直使用r.JSON(200, somestruct),其中r 是来自github.com/martini-contribrender.Render

现在我有一片结构,我想将它们打印为 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
  • 我不希望手动创建(结构有更多字段,但所有字段都是ìnt64float64time.Time

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

【问题讨论】:

    标签: go export-to-csv martini


    【解决方案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 {
                ...
            }
        }
    }
    

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

    // 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 来轻松地将其扩展到更多类型。请参阅 here 以了解其他 Kinds 上的反映文档。

    【讨论】:

    • 您应该添加wr.Flush()。但即便如此,文件会自动下载,而且方法非常手动(-> 我必须手动添加结构的每个字段)。
    • 是的。如果你想要一个通用的解决方案,而不需要为新领域做额外的工作,你必须使用反射。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-22
    • 2016-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多