2015-03-21 4 views
-1

Я хотел бы распечатать CSV-данные на выходе с мартини. В настоящее время я всегда использовал r.JSON(200, somestruct), где r является render.Render от github.com/martini-contrib.Как я могу выводить CSV с мартини?

Теперь у меня есть кусок структур, и я хотел бы напечатать их как 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, float64 или time.Time.

Как можно реализовать вариант экспорта CSV более простым способом?

ответ

-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 или строковых полей. Вы можете легко расширить его до большего количества типов, добавив случаи в коммутатор в structToStringSlice. См. here для отражения документов на другом Kinds.

+0

Вы должны добавить 'wr.Flush()'. Но даже тогда файл автоматически загружается, и подход очень ручной (-> мне нужно вручную добавить каждое поле структуры). –

+0

Да. Если вы хотите общее решение без дополнительной работы для новых полей, вам нужно использовать рефлекс. – Logiraptor

+0

Хорошо. Не могли бы вы расширить свой ответ и показать, как это делается? –

Смежные вопросы