mirror of
https://github.com/imjasonh/csvstruct
synced 2026-07-08 19:14:56 +00:00
120 lines
2.5 KiB
Go
120 lines
2.5 KiB
Go
// TODO: support DecodeNext(nil) to skip a line
|
|
// TODO: NewEncoder/EncodeNext -- header will be fields in first item...
|
|
// TODO: Encode/Decode map[string]string
|
|
|
|
// Package csvstruct provides methods to decode a CSV file into a struct.
|
|
package csvstruct
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"strconv"
|
|
)
|
|
|
|
// Decoder reads and decodes CSV rows from an input stream.
|
|
type Decoder interface {
|
|
DecodeNext(v interface{}) error
|
|
}
|
|
|
|
type decoder struct {
|
|
hm map[string]int
|
|
r csv.Reader
|
|
}
|
|
|
|
// NewDecoder returns a decoder that reads from r.
|
|
func NewDecoder(r io.Reader) Decoder {
|
|
return &decoder{
|
|
r: *csv.NewReader(r),
|
|
}
|
|
}
|
|
|
|
// DecodeNext reads the next CSV-encoded value from its input and stores it in the value pointed to by v.
|
|
func (d *decoder) DecodeNext(v interface{}) error {
|
|
rv := reflect.ValueOf(v)
|
|
if rv.Kind() != reflect.Ptr || rv.IsNil() {
|
|
return errors.New("must be pointer")
|
|
}
|
|
rv = rv.Elem()
|
|
|
|
t := reflect.ValueOf(v).Elem().Type()
|
|
if t.Kind() != reflect.Struct {
|
|
return errors.New("must be pointer to struct")
|
|
}
|
|
|
|
line, err := d.read()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := 0; i < t.NumField(); i++ {
|
|
f := t.Field(i)
|
|
if f.Anonymous {
|
|
continue
|
|
}
|
|
n := f.Name
|
|
if f.Tag.Get("csv") != "" {
|
|
n = f.Tag.Get("csv")
|
|
if n == "-" {
|
|
continue
|
|
}
|
|
}
|
|
idx, ok := d.hm[n]
|
|
if !ok {
|
|
// Unmapped header value
|
|
continue
|
|
}
|
|
strv := line[idx]
|
|
vf := rv.FieldByName(f.Name)
|
|
if vf.CanSet() {
|
|
switch vf.Kind() {
|
|
case reflect.String:
|
|
vf.SetString(strv)
|
|
case reflect.Int, reflect.Int64:
|
|
i, err := strconv.ParseInt(strv, 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("error decoding: %v", err)
|
|
}
|
|
vf.SetInt(i)
|
|
case reflect.Float64:
|
|
f, err := strconv.ParseFloat(strv, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("error decoding: %v", err)
|
|
}
|
|
vf.SetFloat(f)
|
|
case reflect.Bool:
|
|
b, err := strconv.ParseBool(strv)
|
|
if err != nil {
|
|
return fmt.Errorf("error decoding: %v", err)
|
|
}
|
|
vf.SetBool(b)
|
|
default:
|
|
return fmt.Errorf("can't decode type %v", vf.Type())
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *decoder) read() ([]string, error) {
|
|
if d.hm == nil {
|
|
// First run; read header row
|
|
header, err := d.r.Read()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading headers: %v", err)
|
|
}
|
|
d.hm = reverse(header)
|
|
}
|
|
// Read data row into []string
|
|
return d.r.Read()
|
|
}
|
|
|
|
func reverse(in []string) map[string]int {
|
|
m := make(map[string]int, len(in))
|
|
for i, v := range in {
|
|
m[v] = i
|
|
}
|
|
return m
|
|
}
|