I would like to print CSV-data to the output with martini. Currently, I have always used r.JSON(200, somestruct)
where r
is a render.Render
from github.com/martini-contrib
.
Now I have an slice of structs and I would like to print them as CSV (stringify each field of a single struct and print one struct at one line).
Currently, I do it like this:
r.Data(200, []byte("id,Latitude,Longitude
"))
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)+"
"))
}
But I don't like the way I do it for the following reasons:
http: multiple response.WriteHeader calls
ìnt64
, float64
or time.Time
.How can I implement the CSV export option in a simpler way?
Use the standard library. There is no general solution without reflection, but you can simplify it.
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 {
...
}
}
}
If you need a general solution for any struct, it will require reflect. See 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()
}
so then your example is just:
func handler(rw http.ResponseWriter) {
....
writeToCSV(rw, tour.Packets)
}
The function I've written will only work for int or string fields. You can easily extend this to more types by adding cases to the switch in structToStringSlice. See here for reflect docs on the other Kinds
.