github.com/verygoodsoftwarenotvirus/cartogopher@v0.0.0-20160213112503-f5fe2b6d4bd1/reader.go (about) 1 package cartogopher 2 3 import ( 4 "encoding/csv" 5 "fmt" 6 "io" 7 ) 8 9 // MapReader contains all our necessary data for the various methods to function 10 type MapReader struct { 11 Headers []string 12 HeaderIndexMap map[string]int 13 Reader *csv.Reader 14 } 15 16 // CreateHeaderIndexMap creates a map of header strings to their indices in the array generated by 17 // encoding/csv's reader. For instance, if your CSV file looks something like 18 // this: 19 // --------------------- 20 // | one | two | three | 21 // --------------------- 22 // | A | B | C | 23 // --------------------- 24 // Go's generated array for the header row will be [ "one", "two", "three" ]. 25 // Cartogopher's generated map for the header row will be { "one": 1, "two": 2, "three": 3 } 26 func (m *MapReader) CreateHeaderIndexMap() { 27 headerIndexMap := make(map[string]int, len(m.Headers)) 28 29 for index, header := range m.Headers { 30 headerIndexMap[header] = index 31 } 32 33 m.HeaderIndexMap = headerIndexMap 34 } 35 36 // CreateRowMap takes a given CSV array and returns a map of column names to the values contained therein. 37 // For instance, if your CSV file looks something like this: 38 // --------------------- 39 // | one | two | three | 40 // --------------------- 41 // | A | B | C | 42 // --------------------- 43 // The return result will be: 44 // { 45 // "one": "A", 46 // "two": "B", 47 // "three": "C", 48 // } 49 // Note that this requires the HeaderIndexMap to be created and not a null value. 50 func (m MapReader) CreateRowMap(csvRow []string) map[string]string { 51 result := map[string]string{} 52 for header, index := range m.HeaderIndexMap { 53 result[header] = csvRow[index] 54 } 55 56 return result 57 } 58 59 // Read mimics the built-in CSV reader Read method, returning one row of 60 // the CSV. The only difference here being that obviously we return a 61 // map instead of a slice. 62 func (m MapReader) Read() (map[string]string, error) { 63 csvRow, err := m.Reader.Read() 64 if err != nil { 65 return nil, err 66 } 67 return m.CreateRowMap(csvRow), nil 68 } 69 70 // ReadAll mimics the built-in CSV reader ReadAll method 71 func (m MapReader) ReadAll() ([]map[string]string, error) { 72 records, err := m.Reader.ReadAll() 73 if err != nil { 74 return nil, err 75 } 76 results := []map[string]string{} 77 for _, record := range records { 78 results = append(results, m.CreateRowMap(record)) 79 } 80 return results, nil 81 } 82 83 // NewReader returns a new MapReader struct. It can be created the same way 84 // a regular CSV file is created, by providing it with a reference to a file 85 // reader, ideally one that points to a CSV file. I'm using an interface 86 // here so that, should the need arise, you can provide your CSV to the 87 // package in a variety of non-file based ways. Note that here we read the 88 // first row of the file without setting any non-standard values for the 89 // CSV package's Reader struct. If it becomes apparent that the ability to 90 // change these parameters is vital, then I'm more than happy to figure out 91 // an idiomatic way to accomplish that task. 92 func NewReader(file io.Reader) (*MapReader, error) { 93 // Create our reader 94 reader := csv.NewReader(file) 95 96 // Create our resulting struct 97 output := &MapReader{} 98 99 inputHeaders, err := reader.Read() 100 if err != nil { 101 return nil, err 102 } 103 104 // Use our methods (defined above) to populate our struct fields 105 output.Headers = inputHeaders 106 output.Reader = reader 107 output.CreateHeaderIndexMap() 108 109 if output.HeaderIndexMap == nil { 110 return nil, fmt.Errorf("error assigning header to index map for CSV file") 111 } 112 113 return output, nil 114 }