Skip to content

Commit 4c3b8e6

Browse files
committed
feat: add AlignRecord flag to Decoder
Closes: #63
1 parent 5633865 commit 4c3b8e6

File tree

3 files changed

+76
-1
lines changed

3 files changed

+76
-1
lines changed

decoder.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,21 @@ type Decoder struct {
2525
// provided struct.
2626
DisallowMissingColumns bool
2727

28+
// AlignRecord will cause Decoder to align returned record slice to the
29+
// header in case Reader returns records of different lengths.
30+
//
31+
// This flag is supposed to work with csv.Reader.FieldsPerRecord set to -1
32+
// which may cause this behavior.
33+
//
34+
// When header is longer than the record, it will populate the missing
35+
// records with an empty string.
36+
//
37+
// When header is shorter than the record, it will slice the record to match
38+
// header's length.
39+
//
40+
// When this flag is used, Decoder will not ever return ErrFieldCount.
41+
AlignRecord bool
42+
2843
// If not nil, Map is a function that is called for each field in the csv
2944
// record before decoding the data. It allows mapping certain string values
3045
// for specific columns or types to a known format. Decoder calls Map with
@@ -394,7 +409,15 @@ func (d *Decoder) decodeStruct(v reflect.Value) (err error) {
394409
}
395410

396411
if len(d.record) != len(d.header) {
397-
return ErrFieldCount
412+
if !d.AlignRecord {
413+
return ErrFieldCount
414+
}
415+
416+
if len(d.record) > len(d.header) {
417+
d.record = d.record[:len(d.header)]
418+
} else {
419+
d.record = append(d.record, make([]string, len(d.header)-len(d.record))...)
420+
}
398421
}
399422

400423
return d.unmarshal(d.record, v)

decoder_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2483,6 +2483,56 @@ s,1,3.14,true
24832483
t.Fatal("want err not to be nil")
24842484
}
24852485
})
2486+
2487+
t.Run("align record to header - header longer", func(t *testing.T) {
2488+
csvr := csv.NewReader(strings.NewReader("A,B,C\na,b"))
2489+
csvr.FieldsPerRecord = -1
2490+
dec, err := NewDecoder(csvr)
2491+
if err != nil {
2492+
t.Fatalf("want err == nil; got %v", err)
2493+
}
2494+
dec.AlignRecord = true
2495+
2496+
var data []struct {
2497+
A, B, C string
2498+
}
2499+
if err := dec.Decode(&data); err != nil {
2500+
t.Fatal("did not expect decode fail with:", err)
2501+
}
2502+
2503+
if len(data) != 1 {
2504+
t.Fatalf("expected data to be of length 1 got: %d", len(data))
2505+
}
2506+
2507+
if data[0].A != "a" || data[0].B != "b" || data[0].C != "" {
2508+
t.Errorf("expected \"a\", \"b\" and \"\"; got: %q, %q and %q", data[0].A, data[0].B, data[0].C)
2509+
}
2510+
})
2511+
2512+
t.Run("align record to header - header shorter", func(t *testing.T) {
2513+
csvr := csv.NewReader(strings.NewReader("A,B\na,b,c"))
2514+
csvr.FieldsPerRecord = -1
2515+
dec, err := NewDecoder(csvr)
2516+
if err != nil {
2517+
t.Fatalf("want err == nil; got %v", err)
2518+
}
2519+
dec.AlignRecord = true
2520+
2521+
var data []struct {
2522+
A, B string
2523+
}
2524+
if err := dec.Decode(&data); err != nil {
2525+
t.Fatal("did not expect decode fail with:", err)
2526+
}
2527+
2528+
if len(data) != 1 {
2529+
t.Fatalf("expected data to be of length 1 got: %d", len(data))
2530+
}
2531+
2532+
if data[0].A != "a" || data[0].B != "b" {
2533+
t.Errorf("expected \"a\" and \"b\"; got: %q and %q", data[0].A, data[0].B)
2534+
}
2535+
})
24862536
}
24872537

24882538
func BenchmarkDecode(b *testing.B) {

error.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010

1111
// ErrFieldCount is returned when header's length doesn't match the length of
1212
// the read record.
13+
//
14+
// This Error can be disabled with Decoder.AlignRecord = true.
1315
var ErrFieldCount = errors.New("wrong number of fields in record")
1416

1517
// An UnmarshalTypeError describes a string value that was not appropriate for

0 commit comments

Comments
 (0)