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:
parent
49f48c08ef
commit
22a720a148
2 changed files with 53 additions and 39 deletions
66
csvstruct.go
66
csvstruct.go
|
|
@ -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?
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue