Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions csvutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func countRecords(s []byte) (n int) {
}
}

// Header scans the provided struct type and generates a CSV header for it.
// Header scans the provided struct type, struct slice or struct array and generates a CSV header for it.
//
// Field names are written in the same order as struct fields are defined.
// Embedded struct's fields are treated as if they were part of the outer struct.
Expand All @@ -175,8 +175,8 @@ func countRecords(s []byte) (n int) {
//
// If tag is left empty the default "csv" will be used.
//
// Header will return UnsupportedTypeError if the provided value is nil or is
// not a struct.
// Header will return UnsupportedTypeError if the provided value is nil, is
// not a struct, a struct slice or a struct array.
func Header(v any, tag string) ([]string, error) {
typ, err := valueType(v)
if err != nil {
Expand Down Expand Up @@ -216,10 +216,15 @@ loop:
}

typ := walkType(val.Type())
if typ.Kind() != reflect.Struct {
return nil, &UnsupportedTypeError{Type: typ}
switch typ.Kind() {
case reflect.Struct:
return typ, nil
case reflect.Slice, reflect.Array:
if eTyp := walkType(typ.Elem()); eTyp.Kind() == reflect.Struct {
return eTyp, nil
}
}
return typ, nil
return nil, &UnsupportedTypeError{Type: typ}
}

func newCSVReader(r io.Reader) *csv.Reader {
Expand Down
60 changes: 57 additions & 3 deletions csvutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -627,10 +627,64 @@ func TestHeader(t *testing.T) {
err: &UnsupportedTypeError{Type: reflect.TypeOf(int(0))},
},
{
desc: "slice",
v: []TypeJ{{}},
desc: "slice",
v: []TypeJ{{}},
tag: "csv",
header: []string{"STR", "int", "Bool", "Uint8", "float"},
},
{
desc: "ptr slice",
v: &[]TypeJ{{}},
tag: "csv",
header: []string{"STR", "int", "Bool", "Uint8", "float"},
},
{
desc: "slice with ptr value",
v: []*TypeJ{{}},
tag: "csv",
header: []string{"STR", "int", "Bool", "Uint8", "float"},
},
{
desc: "slice with non-struct",
v: []int{0},
tag: "csv",
err: &UnsupportedTypeError{Type: reflect.TypeOf([]int{0})},
},
{
desc: "two-dimensional slice",
v: [][]TypeJ{{{}}},
tag: "csv",
err: &UnsupportedTypeError{Type: reflect.TypeOf([][]TypeJ{{}})},
},
{
desc: "array",
v: [1]TypeJ{{}},
tag: "csv",
header: []string{"STR", "int", "Bool", "Uint8", "float"},
},
{
desc: "ptr array",
v: &[1]TypeJ{{}},
tag: "csv",
header: []string{"STR", "int", "Bool", "Uint8", "float"},
},
{
desc: "array with ptr value",
v: [1]*TypeJ{{}},
tag: "csv",
header: []string{"STR", "int", "Bool", "Uint8", "float"},
},
{
desc: "array with non-struct",
v: [1]int{0},
tag: "csv",
err: &UnsupportedTypeError{Type: reflect.TypeOf([1]int{0})},
},
{
desc: "two-dimensional array",
v: [1][1]TypeJ{{{}}},
tag: "csv",
err: &UnsupportedTypeError{Type: reflect.TypeOf([]TypeJ{})},
err: &UnsupportedTypeError{Type: reflect.TypeOf([1][1]TypeJ{{}})},
},
{
desc: "nil interface",
Expand Down
40 changes: 40 additions & 0 deletions encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1778,6 +1778,46 @@ func TestEncoder(t *testing.T) {
},
},
},
{
desc: "struct slice",
in: []TypeF{},
out: [][]string{
{
"int",
"pint",
"int8",
"pint8",
"int16",
"pint16",
"int32",
"pint32",
"int64",
"pint64",
"uint",
"puint",
"uint8",
"puint8",
"uint16",
"puint16",
"uint32",
"puint32",
"uint64",
"puint64",
"float32",
"pfloat32",
"float64",
"pfloat64",
"string",
"pstring",
"bool",
"pbool",
"interface",
"pinterface",
"binary",
"pbinary",
},
},
},
{
desc: "ptr to nil interface",
in: &nilIface,
Expand Down