将结构体转换为CSV字符串

huangapple go评论79阅读模式
英文:

Converting Struct to CSV string

问题

你可以使用反射(reflect)包来遍历结构体的字段,并将字段名作为表头,将字段值作为该表头下的各列,用逗号分隔,构建一个CSV字符串。以下是一个实现的示例代码:

package main

import (
	"encoding/csv"
	"fmt"
	"os"
	"reflect"
	"strings"
)

type Data struct {
	id   []string
	col1 []float64
	col2 []float64
}

func main() {
	d := &Data{
		id:   []string{"id_1", "id_1", "id_1", "id_1"},
		col1: []float64{340.384926, 321.385028, 520.341473, 500.385473},
		col2: []float64{123.285031, 4087.284675, -8958.284216, -7612.283668},
	}

	csvString := structToCSV(d)
	fmt.Println(csvString)
}

func structToCSV(data interface{}) string {
	value := reflect.ValueOf(data)
	if value.Kind() != reflect.Ptr || value.IsNil() {
		return ""
	}

	value = value.Elem()
	if value.Kind() != reflect.Struct {
		return ""
	}

	var headers []string
	var rows [][]string

	for i := 0; i < value.NumField(); i++ {
		field := value.Field(i)
		fieldName := value.Type().Field(i).Name
		headers = append(headers, fieldName)

		var row []string
		for j := 0; j < field.Len(); j++ {
			row = append(row, fmt.Sprintf("%v", field.Index(j)))
		}
		rows = append(rows, row)
	}

	var csvBuilder strings.Builder
	csvWriter := csv.NewWriter(&csvBuilder)

	csvWriter.Write(headers)
	for _, row := range rows {
		csvWriter.Write(row)
	}

	csvWriter.Flush()
	return csvBuilder.String()
}

这段代码会输出以下CSV字符串:

id,col1,col2
id_1,340.384926,123.285031
id_1,321.385028,4087.284675
id_1,520.341473,-8958.284216
id_1,500.385473,-7612.283668

这是通过遍历结构体的字段,将字段名作为表头,将字段值作为各列的值,构建的CSV字符串。你可以根据需要进行修改和扩展。

英文:

I have a struct that gets Scanned in after a DB response like the below. Each field is the same len(). I'd like to take this struct and produce a CSV delimited string/

package main
import &quot;fmt&quot;
type Data struct {
id   []string
col1 []float64
col2 []float64
}
func main() {
d := &amp;Data{
id:   []string{&quot;id_1&quot;, &quot;id_1&quot;, &quot;id_1&quot;, &quot;id_1&quot;},
col1: []float64{340.384926, 321.385028, 520.341473, 500.385473},
col2: []float64{123.285031, 4087.284675, -8958.284216, -7612.283668},
}
fmt.Printf(&quot;%+v&quot;, d)
}

I'd like to loop through the struct which I believe is possible using reflect, then construct a CSV string like the following using the struct field names as the header and t he values as individual columns for that header, delimited by a comma.

`
id,col1,col2
id_1,340.384926,123.285031
id_1,321.385028,4087.284675
id_1,520.341473,-8958.284216
id_1,500.385473,-7612.283668
`

What is an efficient way to achieve this?

答案1

得分: 3

如果可能的话,尽量避免使用reflect来遍历结构体,因为这可能会导致性能下降和代码可读性降低。不要陷入X-Y问题的陷阱 - 这里的要求是将Data结构体转换为csv字符串(Y问题),但是X问题是要避免使用Data等结构体类型作为起点。

许多操作csv的Golang包喜欢使用以下类型:

然而,如果无法避免使用Data类型,你可以先编写一个函数,将Data转换为[][]string,同时避免使用reflect

func TransformDataTo2DSlice(d Data) [][]string {

	numRows := len(d.id)
	result := make([][]string, numRows+1)

	// 添加表头行
	result[0] = []string{"id", "col1", "col2"}

	// 添加数据行
	for i := 0; i < numRows; i++ {
		result[i+1] = []string{d.id[i],
			strconv.FormatFloat(d.col1[i], 'f', -1, 64),
			strconv.FormatFloat(d.col2[i], 'f', -1, 64),
		}
	}

	return result
}

接下来,使用encoding/csv中的w.WriteAll()方法将[][]string轻松转换为csv:

func main() {

	d := Data{
		id:   []string{"id_1", "id_1", "id_1", "id_1"},
		col1: []float64{340.384926, 321.385028, 520.341473, 500.385473},
		col2: []float64{123.285031, 4087.284675, -8958.284216, -7612.283668},
	}

	d2dslice := TransformDataTo2DSlice(d)

	// fmt.Printf("%+v", d2dslice)
	// [[id, col1, col2],
	// [id_1, 340.384926, 123.285031],
	// [id_1, 321.385028, 4087.284675],
	// [id_1, 520.341473, -8958.284216],
	// [id_1,500.385473,-7612.283668]]

	w := csv.NewWriter(os.Stdout)
	w.WriteAll(d2dslice)

	if err := w.Error(); err != nil {
		log.Fatalln("error writing csv:", err)
	}

    // 输出到标准输出:
    // id,col1,col2
    // id_1,340.384926,123.285031
    // id_1,321.385028,4087.284675
    // id_1,520.341473,-8958.284216
    // id_1,500.385473,-7612.283668
}

在这里运行上述程序:go-playground

如果要将csv写入字符串变量中,可以传入一个缓冲区:

	buf := new(bytes.Buffer)
	w := csv.NewWriter(buf)
	w.WriteAll(d2dslice)

	if err := w.Error(); err != nil {
		log.Fatalln("error writing csv:", err)
	}

	csvString := buf.String()

	fmt.Printf("%T\n", csvString)  // 打印变量类型
    // string

	fmt.Printf("%+v\n", csvString) // 打印变量值
    // id,col1,col2
    // id_1,340.384926,123.285031
    // id_1,321.385028,4087.284675
    // id_1,520.341473,-8958.284216
    // id_1,500.385473,-7612.283668
英文:

If possible, avoid using reflect to iterate through struct because it can result in decreased performance and reduced code readability. Don't fall for the XY problem - the ask here is to transform Data struct into csv string (Y problem), but the X problem here is avoid using struct type such as Data as starting point.

Many golang packages that manipulate csv prefer:

However, if Data type is unavoidable, you can first write a function that transforms Data into [][]string while avoiding using reflect:

func TransformDataTo2DSlice(d Data) [][]string {
numRows := len(d.id)
result := make([][]string, numRows+1)
// Add header row
result[0] = []string{&quot;id&quot;, &quot;col1&quot;, &quot;col2&quot;}
// Add data rows
for i := 0; i &lt; numRows; i++ {
result[i+1] = []string{d.id[i],
strconv.FormatFloat(d.col1[i], &#39;f&#39;, -1, 64),
strconv.FormatFloat(d.col2[i], &#39;f&#39;, -1, 64),
}
}
return result
}

Next, use w.WriteAll() method from encoding/csv to easily transform [][]string to csv

func main() {
d := Data{
id:   []string{&quot;id_1&quot;, &quot;id_1&quot;, &quot;id_1&quot;, &quot;id_1&quot;},
col1: []float64{340.384926, 321.385028, 520.341473, 500.385473},
col2: []float64{123.285031, 4087.284675, -8958.284216, -7612.283668},
}
d2dslice := TransformDataTo2DSlice(d)
// fmt.Printf(&quot;%+v&quot;, d2dslice)
// [[id, col1, col2],
// [id_1, 340.384926, 123.285031],
// [id_1, 321.385028, 4087.284675],
// [id_1, 520.341473, -8958.284216],
// [id_1,500.385473,-7612.283668]]
w := csv.NewWriter(os.Stdout)
w.WriteAll(d2dslice)
if err := w.Error(); err != nil {
log.Fatalln(&quot;error writing csv:&quot;, err)
}
// stdout:
// id,col1,col2
// id_1,340.384926,123.285031
// id_1,321.385028,4087.284675
// id_1,520.341473,-8958.284216
// id_1,500.385473,-7612.283668
}

Run the program above here: go-playground

To write csv to a string variable instead, pass in a buffer:

	buf := new(bytes.Buffer)
w := csv.NewWriter(buf)
w.WriteAll(d2dslice)
if err := w.Error(); err != nil {
log.Fatalln(&quot;error writing csv:&quot;, err)
}
csvString := buf.String()
fmt.Printf(&quot;%T\n&quot;, csvString)  // print the variable type
// string
fmt.Printf(&quot;%+v\n&quot;, csvString) // print the variable value
// id,col1,col2
// id_1,340.384926,123.285031
// id_1,321.385028,4087.284675
// id_1,520.341473,-8958.284216
// id_1,500.385473,-7612.283668

答案2

得分: 2

使用Golang的encoding/csv包。

https://pkg.go.dev/encoding/csv.

英文:

use golang package encoding/csv.

https://pkg.go.dev/encoding/csv.

答案3

得分: 0

  • 步骤1:将数据结构转换为二维数组[][]string
  • 步骤2:打印二维数组的结果。
package main

import (
	"fmt"
	"log"
	"reflect"
)

type Data struct {
	Id   []string  `json:"id"`
	Col1 []float64 `json:"col1"`
	Col2 []float64 `json:"col2"`
}

func (d *Data) String() string {
	dataType := reflect.TypeOf(*d)
	dataValue := reflect.ValueOf(*d)

	// 步骤1:将数据结构转换为二维数组。
	var result [][]string // [列][每列的行数]
	for col := 0; col < dataType.NumField(); col++ {
		filedType := dataType.Field(col)
		filedValue := dataValue.Field(col)

		if result == nil {
			colMax := dataType.NumField()
			result = make([][]string, colMax)
		}

		var rowMax int
		switch filedValue.Interface().(type) {
		case []string:
			rowMax = len(filedValue.Interface().([]string))
		case []float64:
			rowMax = len(filedValue.Interface().([]float64))
		default:
			log.Fatalf("不支持的类型。%v", filedValue.Interface())
		}
		if result[col] == nil {
			result[col] = make([]string, rowMax+1) // 1 用于表头
		}

		// 表头
		result[col][0] = fmt.Sprintf("%v", filedType.Tag.Get("json"))

		// 数据
		var cell interface{}
		for row := 1; row <= rowMax; row++ {
			switch filedValue.Interface().(type) {
			case []string:
				cell = filedValue.Interface().([]string)[row-1]
			case []float64:
				cell = filedValue.Interface().([]float64)[row-1]
			default:
				log.Fatalf("不支持的类型。%v", filedValue.Interface())
			}
			result[col][row] = fmt.Sprintf("%v", cell)
		}
	}

	// 步骤2:打印二维数组的结果。
	str := ""
	const sep = ","
	for row := 0; row < len(result[0]); row++ {
		for col := 0; col < len(result); col++ {
			str += fmt.Sprintf("%v", result[col][row])
			if col < len(result)-1 {
				str += sep
			}
		}
		if row < len(result[0])-1 {
			str += "\n"
		}
	}
	return str
}

func main() {
	d := &Data{
		Id:   []string{"id_1", "id_1", "id_1", "id_1"},
		Col1: []float64{340.384926, 321.385028, 520.341473, 500.385473},
		Col2: []float64{123.285031, 4087.284675, -8958.284216, -7612.283668},
	}
	fmt.Println(d)
}

输出:

id,col1,col2
id_1,340.384926,123.285031  
id_1,321.385028,4087.284675 
id_1,520.341473,-8958.284216
id_1,500.385473,-7612.283668

<kbd>go-playground</kbd>

英文:
  • Step 1: Convert the data structure into a 2-dimensional array [][]string
  • Step 2: Print the result of the 2-dimensional array.
package main

import (
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;reflect&quot;
)

type Data struct {
	Id   []string  `json:&quot;id&quot;`
	Col1 []float64 `json:&quot;col1&quot;`
	Col2 []float64 `json:&quot;col2&quot;`
}

func (d *Data) String() string {
	dataType := reflect.TypeOf(*d)
	dataValue := reflect.ValueOf(*d)

	// Step 1: Convert the data structure into a 2-dimensional array.
	var result [][]string // [col][how many &#39;row&#39; on each col]
	for col := 0; col &lt; dataType.NumField(); col++ {
		filedType := dataType.Field(col)
		filedValue := dataValue.Field(col)

		if result == nil {
			colMax := dataType.NumField()
			result = make([][]string, colMax)
		}

		var rowMax int
		switch filedValue.Interface().(type) {
		case []string:
			rowMax = len(filedValue.Interface().([]string))
		case []float64:
			rowMax = len(filedValue.Interface().([]float64))
		default:
			log.Fatalf(&quot;not support type. %v&quot;, filedValue.Interface())
		}
		if result[col] == nil {
			result[col] = make([]string, rowMax+1) // 1 for header
		}

		// header
		result[col][0] = fmt.Sprintf(&quot;%v&quot;, filedType.Tag.Get(&quot;json&quot;))

		// data
		var cell any
		for row := 1; row &lt;= rowMax; row++ {
			switch filedValue.Interface().(type) {
			case []string:
				cell = filedValue.Interface().([]string)[row-1]
			case []float64:
				cell = filedValue.Interface().([]float64)[row-1]
			default:
				log.Fatalf(&quot;not support type. %v&quot;, filedValue.Interface())
			}
			result[col][row] = fmt.Sprintf(&quot;%v&quot;, cell)
		}
	}

	// Step 2: Print the result of the 2-dimensional array.
	str := &quot;&quot;
	const sep = &quot;,&quot;
	for row := 0; row &lt; len(result[0]); row++ {
		for col := 0; col &lt; len(result); col++ {
			str += fmt.Sprintf(&quot;%v&quot;, result[col][row])
			if col &lt; len(result)-1 {
				str += sep
			}
		}
		if row &lt; len(result[0])-1 {
			str += &quot;\n&quot;
		}
	}
	return str
}

func main() {
	d := &amp;Data{
		Id:   []string{&quot;id_1&quot;, &quot;id_1&quot;, &quot;id_1&quot;, &quot;id_1&quot;},
		Col1: []float64{340.384926, 321.385028, 520.341473, 500.385473},
		Col2: []float64{123.285031, 4087.284675, -8958.284216, -7612.283668},
	}
	fmt.Println(d)
}

output:

id,col1,col2
id_1,340.384926,123.285031  
id_1,321.385028,4087.284675 
id_1,520.341473,-8958.284216
id_1,500.385473,-7612.283668

<kbd>go-playground</kbd>

huangapple
  • 本文由 发表于 2023年3月15日 08:34:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/75739563.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定