mirror of
https://github.com/imjasonh/csvstruct
synced 2026-07-08 02:55:12 +00:00
more tests, handle unexported and tagged fields moar better
This commit is contained in:
parent
56e5f3185f
commit
4ef24bfd38
4 changed files with 148 additions and 21 deletions
|
|
@ -31,13 +31,12 @@ func NewDecoder(r io.Reader) Decoder {
|
||||||
|
|
||||||
// DecodeNext reads the next CSV-encoded value from its input and stores it in the value pointed to by v.
|
// 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 {
|
func (d *decoder) DecodeNext(v interface{}) error {
|
||||||
rv := reflect.ValueOf(v)
|
|
||||||
|
|
||||||
// v is nil, skip this line and proceed.
|
// v is nil, skip this line and proceed.
|
||||||
if rv.Kind() == reflect.Invalid {
|
if v == nil {
|
||||||
_, err := d.read()
|
_, err := d.read()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
if rv.Kind() != reflect.Ptr {
|
if rv.Kind() != reflect.Ptr {
|
||||||
return errors.New("must be pointer")
|
return errors.New("must be pointer")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,15 +25,15 @@ 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", ""}},
|
||||||
}, {
|
}, {
|
||||||
|
|
|
||||||
57
encode.go
57
encode.go
|
|
@ -14,7 +14,7 @@ type Encoder interface {
|
||||||
|
|
||||||
type encoder struct {
|
type encoder struct {
|
||||||
w csv.Writer
|
w csv.Writer
|
||||||
hw bool
|
hm map[string]int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEncoder returns an encoder that writes to w.
|
// NewEncoder returns an encoder that writes to w.
|
||||||
|
|
@ -24,52 +24,81 @@ func NewEncoder(w io.Writer) Encoder {
|
||||||
|
|
||||||
// EncodeNext writes the CSV encoding of v to the stream.
|
// EncodeNext writes the CSV encoding of v to the stream.
|
||||||
func (e *encoder) EncodeNext(v interface{}) error {
|
func (e *encoder) EncodeNext(v interface{}) error {
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
t := reflect.ValueOf(v).Type()
|
t := reflect.ValueOf(v).Type()
|
||||||
if !e.hw {
|
if e.hm == nil {
|
||||||
headers := make([]string, t.NumField())
|
e.hm = make(map[string]int)
|
||||||
|
headers := []string{}
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
f := t.Field(i)
|
f := t.Field(i)
|
||||||
if f.PkgPath != "" { // Filter unexported fields
|
if f.PkgPath != "" { // Filter unexported fields
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
h := f.Name
|
n := f.Name
|
||||||
if f.Tag.Get("csv") != "" {
|
if f.Tag.Get("csv") != "" {
|
||||||
h = f.Tag.Get("csv")
|
n = f.Tag.Get("csv")
|
||||||
|
if n == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
headers[i] = h
|
headers = append(headers, n)
|
||||||
|
e.hm[n] = i
|
||||||
}
|
}
|
||||||
if err := e.w.Write(headers); err != nil {
|
if err := e.w.Write(headers); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
e.hw = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rv := reflect.ValueOf(v)
|
rv := reflect.ValueOf(v)
|
||||||
row := make([]string, t.NumField())
|
row := []string{}
|
||||||
|
add := false // Whether there has been a row to write in this call.
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
f := t.Field(i)
|
f := t.Field(i)
|
||||||
if f.PkgPath != "" { // Filter unexported fields
|
if f.PkgPath != "" { // Filter unexported fields
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
n := f.Name
|
||||||
|
if f.Tag.Get("csv") != "" {
|
||||||
|
n = f.Tag.Get("csv")
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, ok := e.hm[n]
|
||||||
|
if !ok {
|
||||||
|
// Unmapped header value
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase the row size to fit the new row.
|
||||||
|
for fi >= len(row) {
|
||||||
|
row = append(row, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
add = true
|
||||||
vf := rv.Field(i)
|
vf := rv.Field(i)
|
||||||
switch vf.Kind() {
|
switch vf.Kind() {
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
row[i] = vf.String()
|
row[fi] = vf.String()
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
row[i] = fmt.Sprintf("%d", vf.Int())
|
row[fi] = fmt.Sprintf("%d", vf.Int())
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
row[i] = fmt.Sprintf("%d", vf.Uint())
|
row[fi] = fmt.Sprintf("%d", vf.Uint())
|
||||||
case reflect.Float64:
|
case reflect.Float64:
|
||||||
row[i] = fmt.Sprintf("%f", vf.Float())
|
row[fi] = fmt.Sprintf("%f", vf.Float())
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
row[i] = fmt.Sprintf("%t", vf.Bool())
|
row[fi] = fmt.Sprintf("%t", vf.Bool())
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("can't decode type %v", f.Type)
|
return fmt.Errorf("can't decode type %v", f.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !add {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if err := e.w.Write(row); err != nil {
|
if err := e.w.Write(row); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
e.w.Flush()
|
e.w.Flush()
|
||||||
return nil
|
return e.w.Error()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,18 @@ func TestEncodeNext(t *testing.T) {
|
||||||
`Foo,Bar,Baz
|
`Foo,Bar,Baz
|
||||||
a,b,c
|
a,b,c
|
||||||
d,e,f
|
d,e,f
|
||||||
|
`,
|
||||||
|
}, {
|
||||||
|
[]row{{"a", "", ""}, {"", "b", ""}},
|
||||||
|
`Foo,Bar,Baz
|
||||||
|
a,"",""
|
||||||
|
"",b,""
|
||||||
|
`,
|
||||||
|
}, {
|
||||||
|
[]row{{"a", "", ""}, {"", "b", ""}},
|
||||||
|
`Foo,Bar,Baz
|
||||||
|
a,"",""
|
||||||
|
"",b,""
|
||||||
`,
|
`,
|
||||||
}} {
|
}} {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
@ -33,3 +45,90 @@ d,e,f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEncode_Unexported(t *testing.T) {
|
||||||
|
r := struct {
|
||||||
|
Exported, unexported string
|
||||||
|
}{"a", "b"}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := NewEncoder(&buf).EncodeNext(r); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
exp := `Exported
|
||||||
|
a
|
||||||
|
`
|
||||||
|
got := buf.String()
|
||||||
|
if got != exp {
|
||||||
|
t.Errorf("unexpected result, got %s, want %s", got, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncode_Tags(t *testing.T) {
|
||||||
|
type row struct {
|
||||||
|
Foo string `csv:"renamed_foo"`
|
||||||
|
Bar string
|
||||||
|
Ignored string `csv:"-"`
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := NewEncoder(&buf).EncodeNext(row{"a", "b", "c"}); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
exp := `renamed_foo,Bar
|
||||||
|
a,b
|
||||||
|
`
|
||||||
|
got := buf.String()
|
||||||
|
if got != exp {
|
||||||
|
t.Errorf("unexpected result, got %s, want %s", got, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncode_NonStrings(t *testing.T) {
|
||||||
|
r := struct {
|
||||||
|
Int int
|
||||||
|
Int64 int64
|
||||||
|
Uint64 uint64
|
||||||
|
Float64 float64
|
||||||
|
Bool bool
|
||||||
|
}{123, -123456789, 123456789, 123.456, true}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := NewEncoder(&buf).EncodeNext(r); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
exp := `Int,Int64,Uint64,Float64,Bool
|
||||||
|
123,-123456789,123456789,123.456000,true
|
||||||
|
`
|
||||||
|
got := buf.String()
|
||||||
|
if got != exp {
|
||||||
|
t.Errorf("unexpected result, got %s, want %s", got, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDifferent(t *testing.T) {
|
||||||
|
type row1 struct {
|
||||||
|
Foo, Bar string
|
||||||
|
}
|
||||||
|
type row2 struct {
|
||||||
|
Baz string
|
||||||
|
}
|
||||||
|
type row3 struct {
|
||||||
|
Bar, Baz string
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
e := NewEncoder(&buf)
|
||||||
|
// Headers are taken from the fields in the first call to EncodeNext.
|
||||||
|
// Further calls add whatever fields they can, and if no fields are
|
||||||
|
// shared then the row is not written.
|
||||||
|
for _, r := range []interface{}{row1{"foo", "bar"}, row2{"baz"}, row3{"bar", "baz"}} {
|
||||||
|
if err := e.EncodeNext(r); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exp := `Foo,Bar
|
||||||
|
foo,bar
|
||||||
|
"",bar
|
||||||
|
`
|
||||||
|
got := buf.String()
|
||||||
|
if got != exp {
|
||||||
|
t.Errorf("unexpected result, got %s, want %s", got, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue