go
69 lines · 8 steps
Parsing CSV into typed structs in Go
A header-driven CSV parser that maps columns by name and converts each field into a strongly typed Product.
Explained by
highlit
1package inventory
2
3import (
4 "encoding/csv"
5 "fmt"
6 "io"
7 "strconv"
8 "strings"
9 "time"
10)
11
12type Product struct {
13 SKU string
14 Name string
15 Price float64
16 InStock int
17 UpdatedAt time.Time
18}
19
20func ParseProducts(r io.Reader) ([]Product, error) {
21 reader := csv.NewReader(r)
22 reader.TrimLeadingSpace = true
23
24 header, err := reader.Read()
25 if err != nil {
26 return nil, fmt.Errorf("read header: %w", err)
27 }
28
29 cols := make(map[string]int, len(header))
30 for i, name := range header {
31 cols[strings.ToLower(strings.TrimSpace(name))] = i
32 }
33
34 var products []Product
35 for line := 2; ; line++ {
36 record, err := reader.Read()
37 if err == io.EOF {
38 break
39 }
40 if err != nil {
41 return nil, fmt.Errorf("line %d: %w", line, err)
42 }
43
44 price, err := strconv.ParseFloat(record[cols["price"]], 64)
45 if err != nil {
46 return nil, fmt.Errorf("line %d: invalid price: %w", line, err)
47 }
48
49 stock, err := strconv.Atoi(record[cols["in_stock"]])
50 if err != nil {
51 return nil, fmt.Errorf("line %d: invalid stock: %w", line, err)
52 }
53
54 updated, err := time.Parse(time.RFC3339, record[cols["updated_at"]])
55 if err != nil {
56 return nil, fmt.Errorf("line %d: invalid timestamp: %w", line, err)
57 }
58
59 products = append(products, Product{
60 SKU: record[cols["sku"]],
61 Name: record[cols["name"]],
62 Price: price,
63 InStock: stock,
64 UpdatedAt: updated,
65 })
66 }
67
68 return products, nil
69}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Mapping header names to column indexes decouples parsing from physical column order.
- 2Wrapping errors with %w and a line number turns a parse failure into an actionable message.
- 3Converting each field at read time yields a fully typed struct instead of raw strings.
Related explainers
go
package theme import ( "fmt"
Per-tenant HTML templates with Gin's renderer
multi-tenancy
concurrency
html-templates
Advanced
8 steps
go
package auth import ( "net/http"
Setting and reading secure session cookies in Go
cookies
session-management
security
Intermediate
6 steps
go
package middleware import ( "net/http"
A maintenance-mode gate in Gin
middleware
atomic-flag
concurrency
Intermediate
8 steps
go
func ServeVideo(c *gin.Context) { id := c.Param("id") video, err := videoRepo.FindByID(c.Request.Context(), id) if err != nil {
Streaming video files with Gin
http streaming
range requests
file serving
Intermediate
6 steps
go
package handler type LineItem struct { SKU string `json:"sku" binding:"required,alphanum"`
Structured validation errors in Gin
validation
json-binding
error-handling
Intermediate
8 steps
go
package deepcopy import ( "bytes"
Deep copying any value with gob in Go
deep-copy
serialization
generics
Intermediate
6 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/parsing-csv-into-typed-structs-in-go-explained-go-c3f0/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.