1

I have a struct representing a dataset that I need to write to a CSV file as a time-series data. This is what I have so far.

type DataFields struct {
    Field1 int,
    Field2 string,
    ...
    Fieldn int
}

func (d DataFields) String() string {
    return fmt.Sprintf("%v,%v,...,%v", Field1, Field2,..., Fieldn)
}

Is there a way I can iterate through the members of the struct and construct a string object using it?

Performance is not really an issue here and I was wondering if there was a way I could generate the string without having to modify the String() function if the structure changed in the future.

EDITED to add my change below:

This is what I ended up with after looking at the answers below.

func (d DataFields) String() string {
    v := reflect.ValueOf(d)
    var csvString string
    for i := 0; i < v.NumField(); i++ {
        csvString = fmt.Sprintf("%v%v,", csvString, v.Field(i).Interface())
    }

    return csvString
}
  • https://github.com/gocarina/gocsv – Kaveh Shahbazian Sep 25 '17 at 08:14
  • The final version you posted has a trailing comma in the CSV representation. This was a problem for me, so I replaced the single line within the `for` loop with: `if i == 0 { formatString = "%v%v" } else { formatString = "%v,%v" } csvString = fmt.Sprintf(formatString, csvString, v.Field(i).Interface())` – Gautam Jan 05 '21 at 13:17

3 Answers3

2

What you are looking for is called reflection. This answer explains how to use it to loop though a struct and get the values.

This is the example the author uses on the other answer:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    x := struct{Foo string; Bar int }{"foo", 2}
    v := reflect.ValueOf(x)
    values := make([]interface{}, v.NumField())

    for i := 0; i < v.NumField(); i++ {
        values[i] = v.Field(i).Interface()
    }

    fmt.Println(values)
}

You can see it working on the go playground.

Topo
  • 4,783
  • 9
  • 48
  • 70
  • Great. This answer was perfect. I was converting the Value field to a String directly like so `v.Field(i).String()` but I was not getting the actual string representation I was looking for. I am guessing the String() was getting invoked on the Value receiver if the underlying concrete type was not a String. Is this expected? – Pradeep Varadharajan Sep 25 '17 at 15:56
  • @PradeepVaradharajan It is expected. Take a look at [package fmt](https://golang.org/pkg/fmt/) for ways to format the fields and you can use `fmt.Sprintf` to convert them to string, Maybe `%v` the default go syntax representation for the value works for you. – Topo Sep 25 '17 at 22:12
1

One way would be to use the reflect package. There is a Value.Field(int) Value method that might be usefull to you. You would essentially first call ValueOf(interface{}) Value with your DataFields, and then have a simple loop calling Field(int) Value on the Value.

Emil L
  • 20,219
  • 3
  • 44
  • 65
0

Another approach is to use code generation which would generate a serializer code for you.

The trade-offs are:

  • Codegen is more compilcated in that it, most of the time, relies on running an external program (though this could be made simpler by employing go run as it's supposed to be always available).

    Every time you make a change to your data type adding or removing a field which has to be serialized, you need to run go generate to regenerate the serializer code.

    On the flip side the resulting code is fast and robust, and the changes to the data type are usually seldom enough.

  • Reflection is simpler in that it does not require thinking about regenerating the code.

    On the flip side, the code which uses reflect is usually ugly and quite hard to understand. And of course it incurs runtime performance penalty.

kostix
  • 51,517
  • 14
  • 93
  • 176