Skip to content

Commit

Permalink
Read table from file is added.
Browse files Browse the repository at this point in the history
  • Loading branch information
cinar committed Jun 11, 2021
1 parent 3a7b22d commit 4e98c21
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 41 deletions.
43 changes: 41 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[![GoDoc](https://godoc.org/github.com/cinar/csv2?status.svg)](https://godoc.org/github.com/cinar/csv2)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Build Status](https://travis-ci.com/cinar/csv2.svg?branch=master)](https://travis-ci.com/cinar/csv2)
[![Build Status](https://travis-ci.com/cinar/csv2.svg?branch=main)](https://travis-ci.com/cinar/csv2)

# Csv2 Go

Expand Down Expand Up @@ -68,11 +68,50 @@ var prices []dailyPrice
Use the [ReadRowsFromFile](https://pkg.go.dev/github.com/cinar/csv2#ReadRowsFromFile) function to read the CSV file into the slice.

```Golang
err := ReadRowsFromFile(testFile, true, &prices)
err := csv2.ReadRowsFromFile(testFile, true, &prices)
if err != nil {
return err
}
```

### Reading as a table

Define a structure for the table.

```Golang
// Stock prices structure for all columns.
type stockPrices struct {
Date []time.Time `format:"2006-01-02 15:04:05-07:00"`
Close []float64
High []float64
Low []float64
Open []float64
Volume []int64
AdjClose []float64
AdjHigh []float64
AdjLow []float64
AdjOpen []float64
AdjVolume []int64
DivCash []float64
SplitFactor []float64
}
```

Define an instance of the table structure.

```Golang
prices := stockPrices{}
```

Use the [ReadTableFromFile](https://pkg.go.dev/github.com/cinar/csv2#ReadRowsFromFile) function to read the CSV file into the table.

```Golang
err := csv2.ReadTableFromFile(testFile, true, &prices)
if err != nil {
t.Fatal(err)
}
```

## License

The source code is provided under MIT License.
143 changes: 105 additions & 38 deletions csv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,111 +32,111 @@ type columnInfo struct {
Format string
}

func setBoolFieldValue(fieldValue reflect.Value, stringValue string) error {
value, err := strconv.ParseBool(stringValue)
func setBoolValue(value reflect.Value, stringValue string) error {
actualValue, err := strconv.ParseBool(stringValue)
if err == nil {
fieldValue.SetBool(value)
value.SetBool(actualValue)
}

return err
}

func setIntFieldValue(fieldValue reflect.Value, stringValue string, bitSize int) error {
value, err := strconv.ParseInt(stringValue, 10, bitSize)
func setIntValue(value reflect.Value, stringValue string, bitSize int) error {
actualValue, err := strconv.ParseInt(stringValue, 10, bitSize)
if err == nil {
fieldValue.SetInt(value)
value.SetInt(actualValue)
}

return err
}

func setUintFieldValue(fieldValue reflect.Value, stringValue string, bitSize int) error {
value, err := strconv.ParseUint(stringValue, 10, bitSize)
func setUintValue(value reflect.Value, stringValue string, bitSize int) error {
actualValue, err := strconv.ParseUint(stringValue, 10, bitSize)
if err == nil {
fieldValue.SetUint(value)
value.SetUint(actualValue)
}

return err
}

func setFloatFieldValue(fieldValue reflect.Value, stringValue string, bitSize int) error {
value, err := strconv.ParseFloat(stringValue, bitSize)
func setFloatValue(value reflect.Value, stringValue string, bitSize int) error {
actualValue, err := strconv.ParseFloat(stringValue, bitSize)
if err == nil {
fieldValue.SetFloat(value)
value.SetFloat(actualValue)
}

return err
}

func setTimeFieldValue(fieldValue reflect.Value, stringValue string, format string) error {
value, err := time.Parse(format, stringValue)
func setTimeValue(value reflect.Value, stringValue string, format string) error {
actualValue, err := time.Parse(format, stringValue)
if err == nil {
fieldValue.Set(reflect.ValueOf(value))
value.Set(reflect.ValueOf(actualValue))
}

return err
}

func setFieldValue(fieldValue reflect.Value, stringValue string, format string) error {
fieldKind := fieldValue.Kind()
func setValue(value reflect.Value, stringValue string, format string) error {
kind := value.Kind()

switch fieldKind {
switch kind {
case reflect.String:
fieldValue.SetString(stringValue)
value.SetString(stringValue)
return nil

case reflect.Bool:
return setBoolFieldValue(fieldValue, stringValue)
return setBoolValue(value, stringValue)

case reflect.Int:
return setIntFieldValue(fieldValue, stringValue, bits.UintSize)
return setIntValue(value, stringValue, bits.UintSize)

case reflect.Int8:
return setIntFieldValue(fieldValue, stringValue, 8)
return setIntValue(value, stringValue, 8)

case reflect.Int16:
return setIntFieldValue(fieldValue, stringValue, 16)
return setIntValue(value, stringValue, 16)

case reflect.Int32:
return setIntFieldValue(fieldValue, stringValue, 32)
return setIntValue(value, stringValue, 32)

case reflect.Int64:
return setIntFieldValue(fieldValue, stringValue, 64)
return setIntValue(value, stringValue, 64)

case reflect.Uint:
return setUintFieldValue(fieldValue, stringValue, bits.UintSize)
return setUintValue(value, stringValue, bits.UintSize)

case reflect.Uint8:
return setUintFieldValue(fieldValue, stringValue, 8)
return setUintValue(value, stringValue, 8)

case reflect.Uint16:
return setUintFieldValue(fieldValue, stringValue, 16)
return setUintValue(value, stringValue, 16)

case reflect.Uint32:
return setUintFieldValue(fieldValue, stringValue, 32)
return setUintValue(value, stringValue, 32)

case reflect.Uint64:
return setUintFieldValue(fieldValue, stringValue, 64)
return setUintValue(value, stringValue, 64)

case reflect.Float32:
return setFloatFieldValue(fieldValue, stringValue, 32)
return setFloatValue(value, stringValue, 32)

case reflect.Float64:
return setFloatFieldValue(fieldValue, stringValue, 64)
return setFloatValue(value, stringValue, 64)

case reflect.Struct:
fieldTypeString := fieldValue.Type().String()
typeString := value.Type().String()

switch fieldTypeString {
switch typeString {
case "time.Time":
return setTimeFieldValue(fieldValue, stringValue, format)
return setTimeValue(value, stringValue, format)

default:
return fmt.Errorf("unsupported struct type %s", fieldTypeString)
return fmt.Errorf("unsupported struct type %s", typeString)
}

default:
return fmt.Errorf("unsupported field kind %s", fieldKind)
return fmt.Errorf("unsupported value kind %s", kind)
}
}

Expand Down Expand Up @@ -227,7 +227,7 @@ func ReadRowsFromReader(reader io.Reader, hasHeader bool, rows interface{}) erro
row := reflect.New(rowType).Elem()

for _, column := range columns {
if err = setFieldValue(row.Field(column.FieldIndex), record[column.ColumnIndex], column.Format); err != nil {
if err = setValue(row.Field(column.FieldIndex), record[column.ColumnIndex], column.Format); err != nil {
return err
}
}
Expand All @@ -251,3 +251,70 @@ func ReadRowsFromFile(fileName string, hasHeader bool, rows interface{}) error {

return ReadRowsFromReader(file, hasHeader, rows)
}

// Read table from reader.
func ReadTableFromReader(reader io.Reader, hasHeader bool, table interface{}) error {
tablePtrType := reflect.TypeOf(table)
if tablePtrType.Kind() != reflect.Ptr {
return errors.New("table not a pointer")
}

tableType := tablePtrType.Elem()
if tableType.Kind() != reflect.Struct {
return errors.New("table not a pointer to struct")
}

for i := 0; i < tableType.NumField(); i++ {
if tableType.Field(i).Type.Kind() != reflect.Slice {
return errors.New("table fields must be all slices")
}
}

tableValue := reflect.ValueOf(table).Elem()

columns := getStructFieldsAsColumns(tableType)

csvReader := csv.NewReader(reader)

if hasHeader {
if err := readHeader(*csvReader, columns); err != nil {
return err
}
}

for {
record, err := csvReader.Read()
if err == io.EOF {
break
}

if err != nil {
return err
}

for _, column := range columns {
sliceValue := tableValue.Field(column.FieldIndex)

itemValue := reflect.New(sliceValue.Type().Elem()).Elem()
if err = setValue(itemValue, record[column.ColumnIndex], column.Format); err != nil {
return err
}

sliceValue.Set(reflect.Append(sliceValue, itemValue))
}
}

return nil
}

// Read table from file.
func ReadTableFromFile(fileName string, hasHeader bool, rows interface{}) error {
file, err := os.Open(fileName)
if err != nil {
return err
}

defer file.Close()

return ReadTableFromReader(file, hasHeader, rows)
}
15 changes: 14 additions & 1 deletion csv2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type dailyPrice struct {

// Stock prices structure for all columns.
type stockPrices struct {
Date []time.Time
Date []time.Time `format:"2006-01-02 15:04:05-07:00"`
Close []float64
High []float64
Low []float64
Expand All @@ -57,3 +57,16 @@ func TestReadRowsFromFile(t *testing.T) {
t.Fatalf("prices must have 10 element but has %d", n)
}
}

func TestReadTableFromFile(t *testing.T) {
prices := stockPrices{}

err := ReadTableFromFile(testFile, true, &prices)
if err != nil {
t.Fatal(err)
}

if n := len(prices.Date); n != 10 {
t.Fatalf("date must have 10 elements but has %d", n)
}
}

0 comments on commit 4e98c21

Please sign in to comment.