github.com/fluhus/gostuff@v0.4.1-0.20240331134726-be71864f2b5d/csvdec/decoder.go (about) 1 // Package csvdec provides a generic CSV decoder. Wraps the encoding/csv 2 // package with a decoder that can populate structs and slices. 3 package csvdec 4 5 import ( 6 "encoding/csv" 7 "fmt" 8 "io" 9 "reflect" 10 ) 11 12 // A Decoder reads CSV lines and converts them to data objects. Embeds a 13 // csv.Reader, so it can be used the same way. 14 type Decoder struct { 15 *csv.Reader 16 SkipCols uint // How many columns to skip from the beginning of each line. 17 } 18 19 // NewDecoder returns a new decoder that reads from r. skipRows and skipCols 20 // indicate how many of the first rows and columns should be ignored. 21 func NewDecoder(r io.Reader) *Decoder { 22 reader := csv.NewReader(r) 23 return &Decoder{reader, 0} 24 } 25 26 // SkipRow skips a row and returns an error if reading failed. 27 func (d *Decoder) SkipRow() error { 28 _, err := d.Read() 29 return err 30 } 31 32 // Decode reads the next CSV line and populates the given object with parsed 33 // values. Accepted input types are struct pointers and slice pointers, as 34 // explained below. 35 // 36 // Struct pointer: all fields must be exported and of type int*, uint* float*, 37 // string or bool. Fields will be populated by order of appearance. Too few 38 // values in the CSV line will result in an error. Excess values in the CSV 39 // line will be ignored. The struct's last field may be a slice, in which case 40 // all the remaining values will be parsed for that slice's type, according to 41 // the restrictions below. 42 // 43 // Slice pointer of type int*, uint*, float*, string: the pointer will be 44 // populated with a slice of parsed values, according to the length of the CSV 45 // line. 46 // 47 // Any other type will cause a panic. 48 func (d *Decoder) Decode(a interface{}) error { 49 fields, err := d.Read() 50 if err != nil { 51 return err 52 } 53 54 // Skip columns. 55 if uint(len(fields)) < d.SkipCols { 56 return fmt.Errorf("cannot skip %v columns, found only %v columns", 57 d.SkipCols, len(fields)) 58 } 59 fields = fields[d.SkipCols:] 60 61 // Act according to type. 62 value := reflect.ValueOf(a) 63 if value.Kind() != reflect.Ptr { 64 panic("Input must be a pointer. Got: " + value.Type().String()) 65 } 66 elem := value.Elem() 67 typ := elem.Type() 68 69 switch typ.Kind() { 70 case reflect.Struct: 71 return fillStruct(elem, fields) 72 case reflect.Slice: 73 return fillSlice(elem, fields) 74 default: 75 panic("Unsupported type: " + value.Type().String()) 76 } 77 }