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  }