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.
|
||||
func (d *decoder) DecodeNext(v interface{}) error {
|
||||
rv := reflect.ValueOf(v)
|
||||
|
||||
// v is nil, skip this line and proceed.
|
||||
if rv.Kind() == reflect.Invalid {
|
||||
if v == nil {
|
||||
_, err := d.read()
|
||||
return err
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
return errors.New("must be pointer")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,15 +25,15 @@ d,e,f
|
|||
}, {
|
||||
// Rows that only have partial data are only partially filled.
|
||||
data: `Foo,Bar,Baz
|
||||
a,,
|
||||
,b,
|
||||
a,"",""
|
||||
"",b,""
|
||||
`,
|
||||
out: []row{{"a", "", ""}, {"", "b", ""}},
|
||||
}, {
|
||||
// Rows that don't define all the columns are partially filled.
|
||||
data: `Foo,Bar
|
||||
a,
|
||||
,b
|
||||
a,""
|
||||
"",b
|
||||
`,
|
||||
out: []row{{"a", "", ""}, {"", "b", ""}},
|
||||
}, {
|
||||
|
|
|
|||
57
encode.go
57
encode.go
|
|
@ -14,7 +14,7 @@ type Encoder interface {
|
|||
|
||||
type encoder struct {
|
||||
w csv.Writer
|
||||
hw bool
|
||||
hm map[string]int
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (e *encoder) EncodeNext(v interface{}) error {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
t := reflect.ValueOf(v).Type()
|
||||
if !e.hw {
|
||||
headers := make([]string, t.NumField())
|
||||
if e.hm == nil {
|
||||
e.hm = make(map[string]int)
|
||||
headers := []string{}
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if f.PkgPath != "" { // Filter unexported fields
|
||||
continue
|
||||
}
|
||||
h := f.Name
|
||||
n := f.Name
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
e.hw = true
|
||||
}
|
||||
|
||||
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++ {
|
||||
f := t.Field(i)
|
||||
if f.PkgPath != "" { // Filter unexported fields
|
||||
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)
|
||||
switch vf.Kind() {
|
||||
case reflect.String:
|
||||
row[i] = vf.String()
|
||||
row[fi] = vf.String()
|
||||
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:
|
||||
row[i] = fmt.Sprintf("%d", vf.Uint())
|
||||
row[fi] = fmt.Sprintf("%d", vf.Uint())
|
||||
case reflect.Float64:
|
||||
row[i] = fmt.Sprintf("%f", vf.Float())
|
||||
row[fi] = fmt.Sprintf("%f", vf.Float())
|
||||
case reflect.Bool:
|
||||
row[i] = fmt.Sprintf("%t", vf.Bool())
|
||||
row[fi] = fmt.Sprintf("%t", vf.Bool())
|
||||
default:
|
||||
return fmt.Errorf("can't decode type %v", f.Type)
|
||||
}
|
||||
}
|
||||
if !add {
|
||||
return nil
|
||||
}
|
||||
if err := e.w.Write(row); err != nil {
|
||||
return err
|
||||
}
|
||||
e.w.Flush()
|
||||
return nil
|
||||
return e.w.Error()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,18 @@ func TestEncodeNext(t *testing.T) {
|
|||
`Foo,Bar,Baz
|
||||
a,b,c
|
||||
d,e,f
|
||||
`,
|
||||
}, {
|
||||
[]row{{"a", "", ""}, {"", "b", ""}},
|
||||
`Foo,Bar,Baz
|
||||
a,"",""
|
||||
"",b,""
|
||||
`,
|
||||
}, {
|
||||
[]row{{"a", "", ""}, {"", "b", ""}},
|
||||
`Foo,Bar,Baz
|
||||
a,"",""
|
||||
"",b,""
|
||||
`,
|
||||
}} {
|
||||
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