2014-05-15 10:32:47 -04:00
|
|
|
// Package csvstruct provides methods to decode a CSV file into a struct.
|
2014-05-14 20:54:19 -04:00
|
|
|
package csvstruct
|
2014-01-08 06:34:16 -08:00
|
|
|
|
|
|
|
|
import (
|
2014-12-20 22:50:36 -05:00
|
|
|
"encoding"
|
2014-01-08 06:34:16 -08:00
|
|
|
"encoding/csv"
|
2014-05-15 08:31:35 -04:00
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
2014-01-08 06:34:16 -08:00
|
|
|
"io"
|
|
|
|
|
"reflect"
|
2014-05-15 11:22:29 -04:00
|
|
|
"strconv"
|
2014-12-22 16:50:22 -05:00
|
|
|
"strings"
|
2014-01-08 06:34:16 -08:00
|
|
|
)
|
|
|
|
|
|
2014-12-20 22:50:36 -05:00
|
|
|
var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
|
|
|
|
|
2014-05-15 10:32:47 -04:00
|
|
|
// Decoder reads and decodes CSV rows from an input stream.
|
2014-05-14 20:54:19 -04:00
|
|
|
type Decoder interface {
|
2014-06-18 11:50:21 -04:00
|
|
|
// DecodeNext populates v with the values from the next row in the
|
|
|
|
|
// Decoder's Reader.
|
|
|
|
|
//
|
|
|
|
|
// On the first call to DecodeNext, the first row in the reader will be
|
|
|
|
|
// used as the header row to map CSV fields to struct fields, and the
|
|
|
|
|
// second row will be read to populate v.
|
2014-05-14 20:54:19 -04:00
|
|
|
DecodeNext(v interface{}) error
|
2014-12-19 14:35:00 -05:00
|
|
|
|
|
|
|
|
// Opts specifies options to modify decoding behavior.
|
|
|
|
|
//
|
|
|
|
|
// It returns the Decoder, to support chaining.
|
2014-12-21 00:04:25 -05:00
|
|
|
Opts(DecodeOpts) Decoder
|
2014-01-08 06:34:16 -08:00
|
|
|
}
|
|
|
|
|
|
2014-07-17 14:15:40 -04:00
|
|
|
// DecodeOpts specifies options to modify decoding behavior.
|
2014-12-21 00:04:25 -05:00
|
|
|
type DecodeOpts struct {
|
2014-07-17 14:15:40 -04:00
|
|
|
Comma rune // field delimiter (set to ',' by default)
|
|
|
|
|
Comment rune // comment character for start of line
|
|
|
|
|
LazyQuotes bool // allow lazy quotes
|
|
|
|
|
TrimLeadingSpace bool // trim leading space
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-14 20:54:19 -04:00
|
|
|
type decoder struct {
|
2014-12-19 14:35:00 -05:00
|
|
|
r csv.Reader
|
|
|
|
|
hm map[string]int
|
2014-01-08 06:34:16 -08:00
|
|
|
}
|
|
|
|
|
|
2014-06-18 11:50:21 -04:00
|
|
|
// NewDecoder returns a Decoder that reads from r.
|
2014-05-14 20:54:19 -04:00
|
|
|
func NewDecoder(r io.Reader) Decoder {
|
2014-12-19 14:35:00 -05:00
|
|
|
csvr := csv.NewReader(r)
|
|
|
|
|
return &decoder{r: *csvr}
|
2014-07-17 14:15:40 -04:00
|
|
|
}
|
|
|
|
|
|
2014-12-21 00:04:25 -05:00
|
|
|
func (d *decoder) Opts(opts DecodeOpts) Decoder {
|
2014-07-17 14:15:40 -04:00
|
|
|
if opts.Comma != rune(0) {
|
2014-12-19 14:35:00 -05:00
|
|
|
d.r.Comma = opts.Comma
|
2014-01-08 06:34:16 -08:00
|
|
|
}
|
2014-07-17 14:15:40 -04:00
|
|
|
if opts.Comment != rune(0) {
|
2014-12-19 14:35:00 -05:00
|
|
|
d.r.Comment = opts.Comment
|
2014-07-17 14:15:40 -04:00
|
|
|
}
|
2014-12-19 14:35:00 -05:00
|
|
|
d.r.LazyQuotes = opts.LazyQuotes
|
|
|
|
|
d.r.TrimLeadingSpace = opts.TrimLeadingSpace
|
|
|
|
|
return d
|
2014-01-08 06:34:16 -08:00
|
|
|
}
|
|
|
|
|
|
2014-05-15 10:07:07 -04:00
|
|
|
func (d *decoder) DecodeNext(v interface{}) error {
|
2014-07-17 15:49:10 -04:00
|
|
|
line, err := d.read()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-15 12:55:24 -04:00
|
|
|
// v is nil, skip this line and proceed.
|
2014-05-15 14:56:55 -04:00
|
|
|
if v == nil {
|
2014-07-17 15:49:10 -04:00
|
|
|
return nil
|
2014-05-15 12:55:24 -04:00
|
|
|
}
|
2014-07-17 15:49:10 -04:00
|
|
|
|
2014-05-15 14:56:55 -04:00
|
|
|
rv := reflect.ValueOf(v)
|
2014-05-15 12:55:24 -04:00
|
|
|
if rv.Kind() != reflect.Ptr {
|
2014-05-15 10:07:07 -04:00
|
|
|
return errors.New("must be pointer")
|
2014-05-14 20:54:19 -04:00
|
|
|
}
|
2014-05-15 10:07:07 -04:00
|
|
|
rv = rv.Elem()
|
2014-07-17 15:49:10 -04:00
|
|
|
|
2014-07-17 18:18:13 -04:00
|
|
|
switch rv.Type().Kind() {
|
|
|
|
|
case reflect.Map:
|
|
|
|
|
return d.decodeMap(v, line)
|
|
|
|
|
case reflect.Struct:
|
|
|
|
|
return d.decodeStruct(v, line)
|
|
|
|
|
default:
|
2014-07-17 15:49:10 -04:00
|
|
|
return errors.New("must be pointer to struct")
|
2014-05-15 08:31:35 -04:00
|
|
|
}
|
2014-07-17 18:18:13 -04:00
|
|
|
}
|
|
|
|
|
func (d *decoder) decodeMap(v interface{}, line []string) error {
|
|
|
|
|
rv := reflect.ValueOf(v)
|
|
|
|
|
t := rv.Elem().Type()
|
|
|
|
|
if t.Key().Kind() != reflect.String {
|
|
|
|
|
return errors.New("map key must be string")
|
|
|
|
|
}
|
|
|
|
|
switch t.Elem().Kind() {
|
|
|
|
|
case reflect.String:
|
|
|
|
|
m := *(v.(*map[string]string))
|
|
|
|
|
for hv, hidx := range d.hm {
|
|
|
|
|
m[hv] = line[hidx]
|
|
|
|
|
}
|
|
|
|
|
// TODO: Support arbitrary map values by parsing string values
|
|
|
|
|
case reflect.Interface:
|
|
|
|
|
return errors.New("TODO")
|
|
|
|
|
default:
|
|
|
|
|
return fmt.Errorf("can't decode type %v", t.Elem().Kind())
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2014-05-15 08:31:35 -04:00
|
|
|
|
2014-07-17 18:18:13 -04:00
|
|
|
func (d *decoder) decodeStruct(v interface{}, line []string) error {
|
|
|
|
|
rv := reflect.ValueOf(v).Elem()
|
|
|
|
|
t := rv.Type()
|
2014-05-14 20:54:19 -04:00
|
|
|
for i := 0; i < t.NumField(); i++ {
|
|
|
|
|
f := t.Field(i)
|
2014-05-15 08:31:35 -04:00
|
|
|
if f.Anonymous {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2014-05-14 20:54:19 -04:00
|
|
|
n := f.Name
|
2014-12-22 16:50:22 -05:00
|
|
|
omitempty := false
|
|
|
|
|
if tag := f.Tag.Get("csv"); tag != "" {
|
|
|
|
|
parts := strings.Split(tag, ",")
|
2014-12-22 17:00:56 -05:00
|
|
|
if tagn := parts[0]; tag == "-" {
|
2014-05-15 10:42:25 -04:00
|
|
|
continue
|
2014-12-22 17:00:56 -05:00
|
|
|
} else if tagn != "" {
|
|
|
|
|
n = tagn
|
2014-05-15 10:42:25 -04:00
|
|
|
}
|
2014-12-22 16:50:22 -05:00
|
|
|
omitempty = len(parts) > 1 && parts[1] == "omitempty"
|
2014-05-15 10:42:25 -04:00
|
|
|
}
|
2014-05-15 10:07:07 -04:00
|
|
|
idx, ok := d.hm[n]
|
|
|
|
|
if !ok {
|
|
|
|
|
// Unmapped header value
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
strv := line[idx]
|
2014-05-15 10:42:25 -04:00
|
|
|
vf := rv.FieldByName(f.Name)
|
2014-05-15 10:32:47 -04:00
|
|
|
if vf.CanSet() {
|
2014-12-20 22:50:36 -05:00
|
|
|
if vf.CanInterface() && vf.Type().Implements(textUnmarshalerType) {
|
|
|
|
|
if vf.IsNil() {
|
|
|
|
|
vf.Set(reflect.New(vf.Type().Elem()))
|
|
|
|
|
}
|
|
|
|
|
if tu, ok := vf.Interface().(encoding.TextUnmarshaler); ok {
|
|
|
|
|
if err := tu.UnmarshalText([]byte(strv)); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
} else {
|
|
|
|
|
panic("unreachable")
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-12-22 16:50:22 -05:00
|
|
|
if vf.Kind() == reflect.Ptr {
|
|
|
|
|
if omitempty && strv == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if vf.IsNil() {
|
|
|
|
|
vf.Set(reflect.New(vf.Type().Elem()))
|
|
|
|
|
}
|
|
|
|
|
vf = vf.Elem()
|
|
|
|
|
}
|
2014-12-20 22:50:36 -05:00
|
|
|
|
2014-05-15 11:22:29 -04:00
|
|
|
switch vf.Kind() {
|
|
|
|
|
case reflect.String:
|
|
|
|
|
vf.SetString(strv)
|
2014-05-15 12:20:41 -04:00
|
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
2014-05-15 11:22:29 -04:00
|
|
|
i, err := strconv.ParseInt(strv, 10, 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error decoding: %v", err)
|
|
|
|
|
}
|
|
|
|
|
vf.SetInt(i)
|
2014-05-15 12:20:41 -04:00
|
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
|
|
|
u, err := strconv.ParseUint(strv, 10, 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error decoding: %v", err)
|
|
|
|
|
}
|
|
|
|
|
vf.SetUint(u)
|
2014-05-15 11:22:29 -04:00
|
|
|
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())
|
|
|
|
|
}
|
2014-05-15 10:32:47 -04:00
|
|
|
}
|
2014-01-08 06:34:16 -08:00
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-15 10:07:07 -04:00
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-08 06:34:16 -08:00
|
|
|
func reverse(in []string) map[string]int {
|
|
|
|
|
m := make(map[string]int, len(in))
|
|
|
|
|
for i, v := range in {
|
|
|
|
|
m[v] = i
|
|
|
|
|
}
|
|
|
|
|
return m
|
2014-05-14 20:54:19 -04:00
|
|
|
}
|