1
0
Fork 0
mirror of https://github.com/imjasonh/csvstruct synced 2026-07-17 14:32:09 +00:00

I got it to work. Tests pass

This commit is contained in:
Jason Hall 2014-05-15 10:07:07 -04:00
parent 49f48c08ef
commit 22a720a148
2 changed files with 53 additions and 39 deletions

View file

@ -1,3 +1,8 @@
// TODO: struct tags
// TODO: NewEncoder/EncodeNext -- header will be fields in first item...
// TODO: Encode/Decode map[string]string
// TODO: Encode/Decode non-string values?
package csvstruct package csvstruct
import ( import (
@ -5,7 +10,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"reflect" "reflect"
) )
@ -19,47 +23,59 @@ type decoder struct {
} }
func NewDecoder(r io.Reader) Decoder { func NewDecoder(r io.Reader) Decoder {
return decoder{ return &decoder{
r: *csv.NewReader(r), r: *csv.NewReader(r),
} }
} }
func (d decoder) DecodeNext(v interface{}) error { func (d *decoder) DecodeNext(v interface{}) error {
if len(d.hm) == 0 {
// First run; read header row
header, err := d.r.Read()
if err != nil {
return fmt.Errorf("error reading headers: %v", err)
}
d.hm = reverse(header)
}
line, err := d.r.Read()
if err != nil {
return err
}
rv := reflect.ValueOf(v) rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr || rv.IsNil() { if rv.Kind() != reflect.Ptr || rv.IsNil() {
return errors.New("must be pointer") 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
}
t := reflect.TypeOf(rv.Elem())
out := reflect.New(t).Elem()
for i := 0; i < t.NumField(); i++ { for i := 0; i < t.NumField(); i++ {
f := t.Field(i) f := t.Field(i)
if f.Anonymous { if f.Anonymous {
continue continue
} }
n := f.Name n := f.Name
strv := line[d.hm[n]] // TODO: f.Tag.Get("csv") and handle struct tags
log.Printf("%s = %s\n", n, strv) idx, ok := d.hm[n]
out.FieldByName(n).SetString(strv) if !ok {
// Unmapped header value
continue
}
strv := line[idx]
rv.FieldByName(n).SetString(strv)
} }
v = out
return nil 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 { func reverse(in []string) map[string]int {
m := make(map[string]int, len(in)) m := make(map[string]int, len(in))
for i, v := range in { for i, v := range in {
@ -67,7 +83,3 @@ func reverse(in []string) map[string]int {
} }
return m return m
} }
// TODO: NewEncoder/EncodeNext -- header will be fields in first item...
// TODO: Encode/Decode map[string]interface{} ?
// TODO: Encode/Decode non-string values?

View file

@ -18,35 +18,37 @@ func TestDecode(t *testing.T) {
}{{ }{{
data: `Foo,Bar,Baz data: `Foo,Bar,Baz
a,b,c a,b,c
d,e,f`, d,e,f
`,
out: []row{{"a", "b", "c"}, {"d", "e", "f"}}, out: []row{{"a", "b", "c"}, {"d", "e", "f"}},
}, { }, {
// Rows that only have partial data are only partially filled. // Rows that only have partial data are only partially filled.
data: `Foo,Bar,Baz data: `Foo,Bar,Baz
a,, a,,
,b,`, ,b,
`,
out: []row{{"a", "", ""}, {"", "b", ""}}, out: []row{{"a", "", ""}, {"", "b", ""}},
}, { }, {
// Rows that don't define all the columns are partially filled. // Rows that don't define all the columns are partially filled.
data: `Foo,Bar data: `Foo,Bar
a, a,
,b`, ,b
`,
out: []row{{"a", "", ""}, {"", "b", ""}}, out: []row{{"a", "", ""}, {"", "b", ""}},
/*}, { }, {
// Entirely disjoint columns produce empty structs. // Entirely disjoint columns produce empty structs.
data: `Qux data: `Qux
d`, d
out: []row{{"", "", ""}}, */ `,
out: []row{{}},
}} { }} {
d := NewDecoder(strings.NewReader(c.data)) d := NewDecoder(strings.NewReader(c.data))
rows := []row{} rows := []row{}
var row row var row row
for { for {
err := d.DecodeNext(&row) if err := d.DecodeNext(&row); err == io.EOF {
if err == io.EOF {
break break
} } else if err != nil {
if err != nil {
t.Errorf("%v", err) t.Errorf("%v", err)
break break
} }