Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support TypeUnmarshalCSVWithFields in readEach #277

Merged
merged 2 commits into from
May 20, 2024
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
62 changes: 48 additions & 14 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,22 +203,22 @@ func readToWithErrorHandler(decoder Decoder, errHandler ErrorHandler, out interf
objectIface := reflect.New(outValue.Index(i).Type()).Interface()
outInner := createNewOutInner(outInnerWasPointer, outInnerType)
for j, csvColumnContent := range csvRow {
if fieldInfo, ok := csvHeadersLabels[j]; ok { // Position found accordingly to header name

if outInner.CanInterface() {
fieldTypeUnmarshallerWithKeys, withFieldsOK = objectIface.(TypeUnmarshalCSVWithFields)
if withFieldsOK {
if err := fieldTypeUnmarshallerWithKeys.UnmarshalCSVWithFields(fieldInfo.getFirstKey(), csvColumnContent); err != nil {
parseError := csv.ParseError{
Line: i + 2, //add 2 to account for the header & 0-indexing of arrays
Column: j + 1,
Err: err,
}
return &parseError
if outInner.CanInterface() {
fieldTypeUnmarshallerWithKeys, withFieldsOK = objectIface.(TypeUnmarshalCSVWithFields)
if withFieldsOK {
if err := fieldTypeUnmarshallerWithKeys.UnmarshalCSVWithFields(headers[j], csvColumnContent); err != nil {
parseError := csv.ParseError{
Line: i + 2, //add 2 to account for the header & 0-indexing of arrays
Column: j + 1,
Err: err,
}
continue
return &parseError
}
continue
}
}

if fieldInfo, ok := csvHeadersLabels[j]; ok { // Position found accordingly to header name
value := csvColumnContent
if value == "" {
value = fieldInfo.defaultValue
Expand Down Expand Up @@ -289,8 +289,13 @@ func readEach(decoder SimpleDecoder, errHandler ErrorHandler, c interface{}) err
return err
}
}

var withFieldsOK bool
var fieldTypeUnmarshallerWithKeys TypeUnmarshalCSVWithFields

i := 0
for {
objectIface := reflect.New(outValue.Type().Elem()).Interface()
line, err := decoder.GetCSVRow()
if err == io.EOF {
break
Expand All @@ -299,8 +304,31 @@ func readEach(decoder SimpleDecoder, errHandler ErrorHandler, c interface{}) err
}
outInner := createNewOutInner(outInnerWasPointer, outInnerType)
for j, csvColumnContent := range line {

if outInner.CanInterface() {
fieldTypeUnmarshallerWithKeys, withFieldsOK = objectIface.(TypeUnmarshalCSVWithFields)
if withFieldsOK {
if err := fieldTypeUnmarshallerWithKeys.UnmarshalCSVWithFields(headers[j], csvColumnContent); err != nil {
parseError := csv.ParseError{
Line: i + 2, //add 2 to account for the header & 0-indexing of arrays
Column: j + 1,
Err: err,
}
return &parseError
}

continue
}
}

if fieldInfo, ok := csvHeadersLabels[j]; ok { // Position found accordingly to header name
if err := setInnerField(&outInner, outInnerWasPointer, fieldInfo.IndexChain, csvColumnContent, fieldInfo.omitEmpty); err != nil { // Set field of struct

value := csvColumnContent
if value == "" {
value = fieldInfo.defaultValue
}

if err := setInnerField(&outInner, outInnerWasPointer, fieldInfo.IndexChain, value, fieldInfo.omitEmpty); err != nil { // Set field of struct
parseError := &csv.ParseError{
Line: i + 2, //add 2 to account for the header & 0-indexing of arrays
Column: j + 1,
Expand All @@ -313,6 +341,12 @@ func readEach(decoder SimpleDecoder, errHandler ErrorHandler, c interface{}) err
}
}
}

if withFieldsOK {
reflectedObject := reflect.ValueOf(objectIface)
outInner = reflectedObject.Elem()
}

outValue.Send(outInner)
i++
}
Expand Down
109 changes: 108 additions & 1 deletion decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,57 @@ L:
}
}

// Test_readEach_TypeUnmarshalCSVWithFields test that readEach works with type implementing TypeUnmarshalCSVWithFields for unmarshalling
func Test_readEach_TypeUnmarshalCSVWithFields(t *testing.T) {
b := bytes.NewBufferString(`foo,bar,baz,frop
bb,1,cc,3.14
gg,2,hh,4`)
d := newSimpleDecoderFromReader(b)

c := make(chan UnmarshalCSVWithFieldsSample)
e := make(chan error)
var samples []UnmarshalCSVWithFieldsSample
go func() {
if err := readEach(d, nil, c); err != nil {
e <- err
}
}()
L:
for {
select {
case err := <-e:
t.Fatal(err)
case v, ok := <-c:
if !ok {
break L
}
samples = append(samples, v)

}
}
if len(samples) != 2 {
t.Fatalf("expected 2 sample instances, got %d", len(samples))
}
expected := UnmarshalCSVWithFieldsSample{
Foo: "bb",
Bar: 1,
Baz: "cc",
Frop: 314,
}
if expected != samples[0] {
t.Fatalf("expected first sample %v, got %v", expected, samples[0])
}
expected = UnmarshalCSVWithFieldsSample{
Foo: "gg",
Bar: 2,
Baz: "hh",
Frop: 400,
}
if expected != samples[1] {
t.Fatalf("expected first sample %v, got %v", expected, samples[1])
}
}

func Test_maybeMissingStructFields(t *testing.T) {
structTags := []fieldInfo{
{keys: []string{"foo"}},
Expand Down Expand Up @@ -712,7 +763,7 @@ func (rf *RenamedFloat64Unmarshaler) UnmarshalCSV(csv string) (err error) {
return nil
}

// TestUnmarshalCSVWithFields test that the TestUnmarshalCSVWithFields interface to marshall all the fields works
// TestUnmarshalCSVWithFields test that the TestUnmarshalCSVWithFields interface to unmarshal all the fields works
func TestUnmarshalCSVWithFields(t *testing.T) {
b := []byte(`foo,bar,baz,frop
bar,1,zip,3.14
Expand Down Expand Up @@ -800,6 +851,62 @@ func (e UnmarshalError) Error() string {
return e.msg
}

// TestUnmarshalCSVWithFieldsNoTags test that the TypeUnmarshalCSVWithFields interface works for types without matching csv tags (or no csv tags at all)
func TestUnmarshalCSVWithFieldsNoTags(t *testing.T) {
b := []byte(`foo,bar,baz,frop
bar,1,zip,3.14
baz,2,zap,4.00`)
var samples []NoTagsSample
err := UnmarshalBytes(b, &samples)
if err != nil {
t.Fatalf("UnmarshalCSVWithFields() -> UnmarshalBytes() %v", err)
}

if len(samples) != 2 {
t.Fatalf("expected 2 sample instances, got %d", len(samples))
}

if len(samples[0].Fields) != 4 {
t.Fatalf("expected 4 sample fields in map, got %d", len(samples[0].Fields))
}

checkFieldMap := func(expected map[string]string, actual map[string]string) {
for expectedKey, expectedValue := range expected {
if sampleValue, ok := actual[expectedKey]; ok {
if sampleValue != expectedValue {
t.Fatalf("expected %s key in map to have %s value, got %s", expectedKey, expectedValue, sampleValue)
}
} else {
t.Fatalf("expected map to have key %s", expectedKey)
}
}
}

expectedFields := map[string]string{
"foo": "bar",
"bar": "1",
"baz": "zip",
"frop": "3.14",
}
checkFieldMap(expectedFields, samples[0].Fields)

expectedFields = map[string]string{
"foo": "baz",
"bar": "2",
"baz": "zap",
"frop": "4.00",
}
checkFieldMap(expectedFields, samples[1].Fields)
}

func (u *NoTagsSample) UnmarshalCSVWithFields(key, value string) error {
if u.Fields == nil {
u.Fields = map[string]string{}
}
u.Fields[key] = value
return nil
}

func TestMultipleStructTags(t *testing.T) {
b := bytes.NewBufferString(`foo,BAR,Baz
e,3,b`)
Expand Down
6 changes: 6 additions & 0 deletions sample_structs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,9 @@ type NestedSample struct {
type NestedEmbedSample struct {
InnerStruct
}

type NoTagsSample struct {
Fields map[string]string
}

var _ TypeUnmarshalCSVWithFields = (*NoTagsSample)(nil)
Loading