{"id":29127114,"url":"https://github.com/go-andiamo/csvamp","last_synced_at":"2025-06-30T00:09:03.631Z","repository":{"id":298592325,"uuid":"1000343356","full_name":"go-andiamo/csvamp","owner":"go-andiamo","description":"Go package for reading CSVs into structs","archived":false,"fork":false,"pushed_at":"2025-06-11T22:01:57.000Z","size":30,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-11T22:46:58.448Z","etag":null,"topics":["csv","csv-import","go","golang"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/go-andiamo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-11T16:22:43.000Z","updated_at":"2025-06-11T21:59:31.000Z","dependencies_parsed_at":"2025-06-11T22:47:03.673Z","dependency_job_id":"19de3822-7994-4e52-91bb-79baef0bb2c7","html_url":"https://github.com/go-andiamo/csvamp","commit_stats":null,"previous_names":["go-andiamo/csvamp"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/go-andiamo/csvamp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-andiamo%2Fcsvamp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-andiamo%2Fcsvamp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-andiamo%2Fcsvamp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-andiamo%2Fcsvamp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/go-andiamo","download_url":"https://codeload.github.com/go-andiamo/csvamp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-andiamo%2Fcsvamp/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262685699,"owners_count":23348452,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csv","csv-import","go","golang"],"created_at":"2025-06-30T00:09:02.733Z","updated_at":"2025-06-30T00:09:03.606Z","avatar_url":"https://github.com/go-andiamo.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CSVAMP\n[![GoDoc](https://godoc.org/github.com/go-andiamo/csvamp?status.svg)](https://pkg.go.dev/github.com/go-andiamo/csvamp)\n[![Latest Version](https://img.shields.io/github/v/tag/go-andiamo/csvamp.svg?sort=semver\u0026style=flat\u0026label=version\u0026color=blue)](https://github.com/go-andiamo/csvamp/releases)\n[![Go Report Card](https://goreportcard.com/badge/github.com/go-andiamo/csvamp)](https://goreportcard.com/report/github.com/go-andiamo/csvamp)\n\nRead CSVs directly into structs.\n\n---\n\n## Features\n\n- Minimal reflection use\n  - Field reflection only at mapper create time\n  - Efficient field setters at read time\n- Support for common field types: `bool`,`int`,`int8`,`int16`,`int32`,`int64`,`uint`,`uint8`,`uint16`,`uint32`,`uint64`,`float32`,`float64`,`string`\n  - and pointers to those types\n  - quoted detection on string pointers\n- Support for additional types - when they implement `csvamp.CsvUnmarshaler`, `csvamp.CsvQuotedUnmarshaler` or `encoding.TextUnmarshaler`\n- Support for embedded structs and nested structs\n- Map struct fields to CSV field index or header name (using `csv` tag)\n- Adaptable to varying CSVs\n- Post processor option for validating and/or finalising struct\n- Optional error handler for tracking errors without halting reads\n\n---\n\n## Installation\n\n```bash\ngo get github.com/go-andiamo/csvamp\n```\n\n---\n\n## Examples\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e1. Basic implied field ordering\u003c/strong\u003e\u003c/summary\u003e\n\nWithout specifying any `csv` tags on struct fields, the order of the struct fields implies the CSV field order...\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    FirstName string\n    LastName  string\n    Age       int\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Age\nFrodo,Baggins,50\nSamwise,Gamgee,38\nAragorn,Elessar,87\nLegolas,Greenleaf,2931\nGandalf,The Grey,24000`\n\n    r := mapper.Reader(strings.NewReader(data), nil)\n    recs, err := r.ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", recs)\n}\n```\n\n[try on go-playground](https://go.dev/play/p/1A3tsYXcEgP)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e2. Explicit field indexes\u003c/strong\u003e\u003c/summary\u003e\n\nYou can specify the actual CSV field indexes using the `csv` tag...\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    Age       int    `csv:\"[3]\"`\n    LastName  string `csv:\"[2]\"`\n    FirstName string `csv:\"[1]\"`\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Age\nFrodo,Baggins,50\nSamwise,Gamgee,38\nAragorn,Elessar,87\nLegolas,Greenleaf,2931\nGandalf,The Grey,24000`\n\n    r := mapper.Reader(strings.NewReader(data), nil)\n    recs, err := r.ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", recs)\n}\n```\n\n[try on go-playground](https://go.dev/play/p/p9Lc8mMe5Ic)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e3. Mapping to CSV headers\u003c/strong\u003e\u003c/summary\u003e\n\nYou can use the `csv` tag to map struct fields to explicit CSV headers...\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    Age       int    `csv:\"Age\"`\n    LastName  string `csv:\"Last name\"`\n    FirstName string `csv:\"First name\"`\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Age\nFrodo,Baggins,50\nSamwise,Gamgee,38\nAragorn,Elessar,87\nLegolas,Greenleaf,2931\nGandalf,The Grey,24000`\n\n    r := mapper.Reader(strings.NewReader(data), nil)\n    recs, err := r.ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", recs)\n}\n```\n\n[try on go-playground](https://go.dev/play/p/OvowRsJnCUE)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e4. Capturing the CSV line number\u003c/strong\u003e\u003c/summary\u003e\n\nYou can use a special `csv` tag to capture the CSV line number into the struct...\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    Line      int `csv:\"[line]\"`\n    FirstName string\n    LastName  string\n    Age       int\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Age\nFrodo,Baggins,50\nSamwise,Gamgee,38\nAragorn,Elessar,87\nLegolas,Greenleaf,2931\nGandalf,The Grey,24000`\n\n    r := mapper.Reader(strings.NewReader(data), nil)\n    recs, err := r.ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", recs)\n}\n```\n\n[try on go-playground](https://go.dev/play/p/ZVrBXm7-VsP)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e5. Capturing the CSV record\u003c/strong\u003e\u003c/summary\u003e\n\nYou can use a special `csv` tag to capture the CSV record (line) into the struct...\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    Raw       []string `csv:\"[raw]\"`\n    FirstName string\n    LastName  string\n    Age       int\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Age\nFrodo,Baggins,50\nSamwise,Gamgee,38\nAragorn,Elessar,87\nLegolas,Greenleaf,2931\nGandalf,The Grey,24000`\n\n    r := mapper.Reader(strings.NewReader(data), nil)\n    recs, err := r.ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", recs)\n}\n```\n\n[try on go-playground](https://go.dev/play/p/TBDZCvZBRvv)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e6. Adapting the mapper to varying CSVs\u003c/strong\u003e\u003c/summary\u003e\n\nSometimes, your CSV won't always arrive in the format you're expecting - the mapper can adapt to that...\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    FirstName string\n    LastName  string\n    Age       int\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const sample1 = `First name,Last name,Age\nFrodo,Baggins,50\nSamwise,Gamgee,38\nAragorn,Elessar,87\nLegolas,Greenleaf,2931\nGandalf,The Grey,24000`\n    const sample2 = `Age,First name,Last name\n50,Frodo,Baggins\n38,Samwise,Gamgee\n87,Aragorn,Elessar\n2931,Legolas,Greenleaf\n24000,Gandalf,The Grey`\n\n    recs, err := mapper.Reader(strings.NewReader(sample1), nil).ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", recs)\n\n    m2, err := mapper.Adapt(false, csvamp.OverrideMappings{\n        {\n            FieldName:    \"FirstName\",\n            CsvFieldName: \"First name\",\n        },\n        {\n            FieldName:    \"LastName\",\n            CsvFieldName: \"Last name\",\n        },\n        {\n            FieldName:    \"Age\",\n            CsvFieldName: \"Age\",\n        },\n    })\n    if err != nil {\n        panic(err)\n    }\n    recs, err = m2.Reader(strings.NewReader(sample2), nil).ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", recs)\n}\n```\n\n[try on go-playground](https://go.dev/play/p/xcNunZOCcL8)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e7. Using \u003ccode\u003epostProcessor\u003c/code\u003e to validate\u003c/strong\u003e\u003c/summary\u003e\n\nYou can use the `postProcessor` to validate records...\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    Line      int `csv:\"[line]\"`\n    FirstName string\n    LastName  string\n    Age       int\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Age\nFrodo,Baggins,50\nSamwise,Gamgee,38\nAragorn,Elessar,87\nLegolas,Greenleaf,2931\nGandalf,The Grey,24000`\n\n    r := mapper.Reader(strings.NewReader(data), func(row *Record) error {\n        if row.Age \u003e 200 {\n            return fmt.Errorf(\"age is too high - on line %d\", row.Line)\n        }\n        return nil\n    })\n    recs, err := r.ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", recs)\n}\n```\n\n[try on go-playground](https://go.dev/play/p/AFgn9Dy7YAd)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e8. Using \u003ccode\u003epostProcessor\u003c/code\u003e to adjust struct read\u003c/strong\u003e\u003c/summary\u003e\n\nSometimes, some fields are calculated - you can exclude them from being read using `csv:\"-\"` and then fill them in using a `postProcessor`...\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    FirstName string\n    LastName  string\n    Age       int\n    AgeInDays int `csv:\"-\"`\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Age\nFrodo,Baggins,50\nSamwise,Gamgee,38\nAragorn,Elessar,87\nLegolas,Greenleaf,2931\nGandalf,The Grey,24000`\n\n    r := mapper.Reader(strings.NewReader(data), func(row *Record) error {\n        row.AgeInDays = row.Age * 365\n        return nil\n    })\n    recs, err := r.ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", recs)\n}\n```\n\n[try on go-playground](https://go.dev/play/p/h0gSN5CZ3Df)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e9. Reading one row at a time\u003c/strong\u003e\u003c/summary\u003e\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"io\"\n    \"strings\"\n)\n\ntype Record struct {\n    FirstName string\n    LastName  string\n    Age       int\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Age\nFrodo,Baggins,50\nSamwise,Gamgee,38\nAragorn,Elessar,87\nLegolas,Greenleaf,2931\nGandalf,The Grey,24000`\n\n    r := mapper.Reader(strings.NewReader(data), nil)\n\n    for {\n        record, err := r.Read()\n        if err == io.EOF {\n            break\n        } else if err != nil {\n            panic(err)\n        }\n        fmt.Printf(\"%+v\\n\", record)\n}\n}\n```\n\n[try on go-playground](https://go.dev/play/p/Vot_iCJv1MX)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e10. Iterating over rows\u003c/strong\u003e\u003c/summary\u003e\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    FirstName string\n    LastName  string\n    Age       int\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Age\nFrodo,Baggins,50\nSamwise,Gamgee,38\nAragorn,Elessar,87\nLegolas,Greenleaf,2931\nGandalf,The Grey,24000`\n\n    r := mapper.Reader(strings.NewReader(data), nil)\n\n    err := r.Iterate(func(record Record) (bool, error) {\n        fmt.Printf(\"%+v\\n\", record)\n        return true, nil\n    })\n    if err != nil {\n        panic(err)\n    }\n}\n```\n\n[try on go-playground](https://go.dev/play/p/smNxzXmIlel)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e11. Using \u003ccode\u003eCsvUnmarshaler\u003c/code\u003e (e.g. dates)\u003c/strong\u003e\u003c/summary\u003e\n\nCSVs come in all flavours - and dates (which `csvamp` doesn't handle natively) come in varying formats.  Use types that implement `csvamp.CsvUnmarshaler` to resolve this...\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n    \"time\"\n)\n\ntype DOB time.Time\n\nfunc (d *DOB) UnmarshalCSV(val string, record []string) error {\n    dt, err := time.Parse(\"2006-01-02\", val)\n    if err != nil {\n        return err\n    }\n    *d = DOB(dt)\n    return nil\n}\n\ntype Record struct {\n    FirstName string\n    LastName  string\n    DOB       DOB\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Date Of Birth\nFrodo,Baggins,2968-09-22\nSamwise,Gamgee,2980-04-06\nAragorn,Elessar,2931-03-01`\n\n    r := mapper.Reader(strings.NewReader(data), nil)\n    err := r.Iterate(func(record Record) (bool, error) {\n        fmt.Printf(\"Name: %s %s\\n\", record.FirstName, record.LastName)\n        fmt.Printf(\" DOB: %s\\n\", time.Time(record.DOB).Format(\"Mon, 02 Jan 2006\"))\n        return true, nil\n    })\n    if err != nil {\n        panic(err)\n    }\n}\n```\n\n[try on go-playground](https://go.dev/play/p/sCBMdYV2bM4)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e12. Utilising \u003ccode\u003eencoding.TextUnmarshaler\u003c/code\u003e (e.g. dates)\u003c/strong\u003e\u003c/summary\u003e\n\n`csvamp` only supports 'primitive' types - but, fortunately, many additional types support the `encoding.TextUnmarshaler` interface - \nthis can be utilised.  The following example utilises the fact that `time.Time` implements the `encoding.TextUnmarshaler` interface...\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n    \"time\"\n)\n\ntype Record struct {\n    FirstName string\n    LastName  string\n    DOB       time.Time\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Date Of Birth\nFrodo,Baggins,2968-09-22T00:00:00Z\nSamwise,Gamgee,2980-04-06T00:00:00Z\nAragorn,Elessar,2931-03-01T00:00:00Z`\n\n    r := mapper.Reader(strings.NewReader(data), nil)\n    err := r.Iterate(func(record Record) (bool, error) {\n        fmt.Printf(\"Name: %s %s\\n\", record.FirstName, record.LastName)\n        fmt.Printf(\" DOB: %s\\n\", time.Time(record.DOB).Format(\"Mon, 02 Jan 2006\"))\n        return true, nil\n    })\n    if err != nil {\n        panic(err)\n    }\n}\n```\n\n[try on go-playground](https://go.dev/play/p/yr6LrgCfOVj)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e13. Optional fields? Use pointer types\u003c/strong\u003e\u003c/summary\u003e\n\nWhen a struct field is a pointer type, `csvamp` treats it as optional - if the corresponding CSV field is empty - it is treated as not there at all...\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    FirstName string\n    LastName  string\n    Age       *int\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Age\nFrodo,Baggins,50\nSamwise,Gamgee,38\nAragorn,Elessar,87\nLegolas,Greenleaf,\nGandalf,The Grey,`\n\n    r := mapper.Reader(strings.NewReader(data), nil)\n    err := r.Iterate(func(record Record) (bool, error) {\n        fmt.Printf(\"Name: %s %s\\n\", record.FirstName, record.LastName)\n        if record.Age != nil {\n            fmt.Printf(\"Age: %d\\n\", *record.Age)\n        } else {\n            fmt.Printf(\"Age: %s\\n\", \"(unknown)\")\n        }\n        return true, nil\n    })\n    if err != nil {\n        panic(err)\n    }\n}\n```\n\n[try on go-playground](https://go.dev/play/p/Ppw0ZoBZwwp)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e14. Using error handler to track errors\u003c/strong\u003e\u003c/summary\u003e\n\nWhen using `ReadAll()` (or `Iterate()`) you may not want to halt when an error is encountered... \n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    FirstName string\n    LastName  string\n    Age       int\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Age\nFrodo,Baggins,not a number!\nSamwise,Gamgee,38\nAragorn,Elessar,not a number!\nLegolas,Greenleaf,2931\nGandalf,The Grey,24000`\n\n    eh := \u0026errorHandler{}\n    r := mapper.Reader(strings.NewReader(data), nil).WithErrorHandler(eh)\n    recs, err := r.ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", recs)\n\n    fmt.Printf(\"Found %d errors\\n\", len(eh.errs))\n    for i, err := range eh.errs {\n        fmt.Printf(\"Line %d: %s\\n\", eh.lines[i], err)\n    }\n}\n\ntype errorHandler struct {\n    errs  []error\n    lines []int\n}\n\nfunc (eh *errorHandler) Handle(err error, line int) error {\n    eh.errs = append(eh.errs, err)\n    eh.lines = append(eh.lines, line)\n    return nil\n}\n```\n\n[try on go-playground](https://go.dev/play/p/QLNoDtewIRa)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e15. Manually supply CSV headers\u003c/strong\u003e\u003c/summary\u003e\n\nSometimes, incoming CSV won't have a header line - but your struct fields are mapped to header names.  This can be handled by supplying the headers...\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"github.com/go-andiamo/csvamp/csv\"\n    \"strings\"\n)\n\ntype Record struct {\n    Age       int    `csv:\"Age\"`\n    LastName  string `csv:\"Last name\"`\n    FirstName string `csv:\"First name\"`\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `Frodo,Baggins,50\nSamwise,Gamgee,38\nAragorn,Elessar,87\nLegolas,Greenleaf,2931\nGandalf,The Grey,24000`\n\n    // csv.NoHeader(true) indicates to the reader that the CSV has no header line...\n    r := mapper.Reader(strings.NewReader(data), nil, csv.NoHeader(true)).\n        SupplyHeaders([]string{\"First name\", \"Last name\", \"Age\"})\n    recs, err := r.ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", recs)\n}\n```\n\n[try on go-playground](https://go.dev/play/p/bgTZ12NM11x)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e16. Quoted detection on string pointer fields\u003c/strong\u003e\u003c/summary\u003e\n\nUsing string pointer fields determines whether the CSV field was quoted or un-quoted for empty string or nil...\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    Name *string\n    Age  int\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `Name,Age\n\"\",50\n,38`\n\n    r := mapper.Reader(strings.NewReader(data), nil)\n    recs, err := r.ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    // Name for first record should be pointer to empty string\n    // Name for second record should be nil\n    fmt.Printf(\"%+v\\n\", recs)\n}\n```\n\n[try on go-playground](https://go.dev/play/p/AIbfIS90PUv)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e17. Nested structs\u003c/strong\u003e\u003c/summary\u003e\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    FirstName string\n    LastName  string\n    Age       int\n    Address   Address\n}\n\ntype Address struct {\n    Street string\n    Town   string\n    County string\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Age,Street,Town,County\nFrodo,Baggins,50,1 Bagshot Row,Hobbiton,The Shire\nSamwise,Gamgee,38,2 Bagshot Row,Hobbiton,The Shire\nAragorn,Elessar,87,Royal Quarters,The Citadel,Minas Tirith`\n\n    r := mapper.Reader(strings.NewReader(data), nil)\n    recs, err := r.ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", recs)\n}\n```\n\n[try on go-playground](https://go.dev/play/p/qgPT2hONuZ3)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e18. Embedded structs\u003c/strong\u003e\u003c/summary\u003e\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    FirstName string\n    LastName  string\n    Age       int\n    Address\n}\n\ntype Address struct {\n    Street string\n    Town   string\n    County string\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Age,Street,Town,County\nFrodo,Baggins,50,1 Bagshot Row,Hobbiton,The Shire\nSamwise,Gamgee,38,2 Bagshot Row,Hobbiton,The Shire\nAragorn,Elessar,87,Royal Quarters,The Citadel,Minas Tirith`\n\n    r := mapper.Reader(strings.NewReader(data), nil)\n    recs, err := r.ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", recs)\n}\n```\n\n[try on go-playground](https://go.dev/play/p/uzwjWlb7ZdA)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e19. Nested structs with unmarshalling\u003c/strong\u003e\u003c/summary\u003e\n\nSometimes, you may want a nested struct to dissect a single csv field.  If the nested struct implements `CsvUnmarshaler`, `CsvQuotedUnmarshaler` or `encoding.TextUnmarshaler` interface, the struct field is treated as mapped to a single csv field...\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    FirstName string\n    LastName  string\n    Age       int\n    Address   Address\n}\n\ntype Address struct {\n    Lines []string\n}\n\nfunc (a *Address) UnmarshalCSV(val string, record []string) error {\n    if val != \"\" {\n        a.Lines = strings.Split(val, \"\\n\")\n    }\n    return nil\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Age,Address\nFrodo,Baggins,50,\"1 Bagshot Row\nHobbiton\nThe Shire\"\nSamwise,Gamgee,38,\"2 Bagshot Row\nHobbiton\nThe Shire\"\nAragorn,Elessar,87,\"Royal Quarters\nThe Citadel\nMinas Tirith\"`\n\n    r := mapper.Reader(strings.NewReader(data), nil)\n    recs, err := r.ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", recs)\n}\n```\n\n[try on go-playground](https://go.dev/play/p/PhysaOsaro0)\n\n\u003c/details\u003e\u003cbr\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cstrong\u003e20. Special handling of \u003ccode\u003e[]string\u003c/code\u003e fields\u003c/strong\u003e\u003c/summary\u003e\n\ncsvamp has special handling of `[]string` fields - it treats the quoted csv field as comma separated...\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/go-andiamo/csvamp\"\n    \"strings\"\n)\n\ntype Record struct {\n    FirstName string\n    LastName  string\n    Age       int\n    Address   []string\n}\n\nvar mapper = csvamp.MustNewMapper[Record]()\n\nfunc main() {\n    const data = `First name,Last name,Age,Address\nFrodo,Baggins,50,\"1 Bagshot Row,Hobbiton,The Shire\"\nSamwise,Gamgee,38,\"2 Bagshot Row,Hobbiton,The Shire\"\nAragorn,Elessar,87,\"Royal Quarters,The Citadel,Minas Tirith\"`\n\n    r := mapper.Reader(strings.NewReader(data), nil)\n    recs, err := r.ReadAll()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", recs)\n}\n```\n\n[try on go-playground](https://go.dev/play/p/1oFekDnz1Lc)\n\n\u003c/details\u003e\u003cbr\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgo-andiamo%2Fcsvamp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgo-andiamo%2Fcsvamp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgo-andiamo%2Fcsvamp/lists"}