Skip to main content

encoding/csv

Package csv reads and writes comma-separated values (CSV) files. The package supports the format described in RFC 4180, with customizable delimiters and options.

Overview

The csv package provides:
  1. Reader - For reading CSV data from an io.Reader
  2. Writer - For writing CSV data to an io.Writer
A csv file contains zero or more records of one or more fields per record. Each record is separated by the newline character. White space is considered part of a field.

Types

Reader

type Reader struct {
    Comma            rune // Field delimiter (default ',')
    Comment          rune // Comment character (disabled by default)
    FieldsPerRecord  int  // Number of expected fields per record
    LazyQuotes       bool // Allow lazy quotes
    TrimLeadingSpace bool // Trim leading space
    ReuseRecord      bool // Reuse record slice for performance
}
A Reader reads records from a CSV-encoded file. As returned by NewReader, a Reader expects input conforming to RFC 4180.

NewReader

func NewReader(r io.Reader) *Reader
NewReader returns a new Reader that reads from r.
r
io.Reader
required
The reader to read CSV data from
*Reader
*Reader
A new Reader instance with Comma set to ’,’
Example:
in := `first_name,last_name,username
"Rob","Pike",rob
Ken,Thompson,ken
"Robert","Griesemer","gri"`

r := csv.NewReader(strings.NewReader(in))

for {
    record, err := r.Read()
    if err == io.EOF {
        break
    }
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(record)
}
// Output:
// [first_name last_name username]
// [Rob Pike rob]
// [Ken Thompson ken]
// [Robert Griesemer gri]

Methods

Read
func (r *Reader) Read() (record []string, err error)
Read reads one record (a slice of fields) from r. If the record has an unexpected number of fields, Read returns the record along with the error ErrFieldCount.
record
[]string
A slice of strings representing the fields in the CSV record
err
error
Returns io.EOF when there are no more records. Returns ParseError for parsing errors. Returns ErrFieldCount if FieldsPerRecord is set and the record has the wrong number of fields.
ReadAll
func (r *Reader) ReadAll() (records [][]string, err error)
ReadAll reads all the remaining records from r. Each record is a slice of fields. A successful call returns err == nil, not err == io.EOF.
records
[][]string
A slice of records, where each record is a slice of field strings
err
error
Returns nil on success, or an error if reading fails
Example:
in := `first_name,last_name,username
"Rob","Pike",rob
Ken,Thompson,ken
"Robert","Griesemer","gri"`

r := csv.NewReader(strings.NewReader(in))
records, err := r.ReadAll()
if err != nil {
    log.Fatal(err)
}

fmt.Print(records)
// Output:
// [[first_name last_name username] [Rob Pike rob] [Ken Thompson ken] [Robert Griesemer gri]]
InputOffset
func (r *Reader) InputOffset() int64
InputOffset returns the input stream byte offset of the current reader position. The offset gives the location of the end of the most recently read row and the beginning of the next row.

Configuration Fields

Comma The field delimiter. Set to ’,’ by NewReader. Must be a valid rune and must not be \r, \n, or the Unicode replacement character (0xFFFD). Example:
in := `first_name;last_name;username
"Rob";"Pike";rob`

r := csv.NewReader(strings.NewReader(in))
r.Comma = ';' // Use semicolon as delimiter
Comment If not 0, lines beginning with the Comment character without preceding whitespace are ignored. Must be a valid rune and must not be \r, \n, or the Unicode replacement character. It must also not be equal to Comma.
r := csv.NewReader(strings.NewReader(in))
r.Comment = '#' // Lines starting with # are ignored
FieldsPerRecord The number of expected fields per record:
  • If positive: Read requires each record to have the given number of fields
  • If 0: Read sets it to the number of fields in the first record
  • If negative: No check is made and records may have a variable number of fields
LazyQuotes If true, a quote may appear in an unquoted field and a non-doubled quote may appear in a quoted field. TrimLeadingSpace If true, leading white space in a field is ignored, even if the field delimiter (Comma) is white space. ReuseRecord If true, calls to Read may return a slice sharing the backing array of the previous call’s returned slice for performance. By default, each call to Read returns newly allocated memory.

Writer

type Writer struct {
    Comma   rune // Field delimiter (default ',')
    UseCRLF bool // Use \r\n as line terminator (default false)
}
A Writer writes records using CSV encoding. As returned by NewWriter, a Writer writes records terminated by a newline and uses ’,’ as the field delimiter.

NewWriter

func NewWriter(w io.Writer) *Writer
NewWriter returns a new Writer that writes to w.
w
io.Writer
required
The writer to write CSV data to
*Writer
*Writer
A new Writer instance with Comma set to ’,’
Example:
records := [][]string{
    {"first_name", "last_name", "username"},
    {"Rob", "Pike", "rob"},
    {"Ken", "Thompson", "ken"},
    {"Robert", "Griesemer", "gri"},
}

w := csv.NewWriter(os.Stdout)

for _, record := range records {
    if err := w.Write(record); err != nil {
        log.Fatalln("error writing record to csv:", err)
    }
}

w.Flush()

if err := w.Error(); err != nil {
    log.Fatal(err)
}
// Output:
// first_name,last_name,username
// Rob,Pike,rob
// Ken,Thompson,ken
// Robert,Griesemer,gri

Methods

Write
func (w *Writer) Write(record []string) error
Write writes a single CSV record to w along with any necessary quoting. A record is a slice of strings with each string being one field. Writes are buffered, so Flush must eventually be called to ensure that the record is written to the underlying io.Writer.
record
[]string
required
A slice of strings representing the fields to write
error
error
Returns an error if the delimiter is invalid or if writing fails
WriteAll
func (w *Writer) WriteAll(records [][]string) error
WriteAll writes multiple CSV records to w using Write and then calls Flush, returning any error from the Flush.
records
[][]string
required
A slice of records to write, where each record is a slice of field strings
error
error
Returns any error from writing or flushing
Example:
records := [][]string{
    {"first_name", "last_name", "username"},
    {"Rob", "Pike", "rob"},
    {"Ken", "Thompson", "ken"},
    {"Robert", "Griesemer", "gri"},
}

w := csv.NewWriter(os.Stdout)
w.WriteAll(records) // calls Flush internally

if err := w.Error(); err != nil {
    log.Fatalln("error writing csv:", err)
}
Flush
func (w *Writer) Flush()
Flush writes any buffered data to the underlying io.Writer. To check if an error occurred during Flush, call Error. Error
func (w *Writer) Error() error
Error reports any error that has occurred during a previous Write or Flush.

Configuration Fields

Comma The field delimiter. Set to ’,’ by NewWriter. Must be a valid rune and must not be \r, \n, the Unicode replacement character (0xFFFD), or a quote character.
w := csv.NewWriter(os.Stdout)
w.Comma = ';' // Use semicolon as delimiter
UseCRLF If true, the Writer ends each output line with \r\n instead of \n.
w := csv.NewWriter(os.Stdout)
w.UseCRLF = true // Use Windows-style line endings

Error Types

ParseError

type ParseError struct {
    StartLine int   // Line where the record starts
    Line      int   // Line where the error occurred
    Column    int   // Column (1-based byte index) where the error occurred
    Err       error // The actual error
}
A ParseError is returned for parsing errors. Line and column numbers are 1-indexed.

Error Variables

var ErrBareQuote = errors.New("bare \" in non-quoted-field")
var ErrQuote = errors.New("extraneous or missing \" in quoted-field")
var ErrFieldCount = errors.New("wrong number of fields")
These errors can be returned in ParseError.Err:
  • ErrBareQuote - A bare quote appeared in an unquoted field
  • ErrQuote - An extraneous or missing quote appeared in a quoted field
  • ErrFieldCount - The record has the wrong number of fields

CSV Format

Field Quoting

Fields that start and stop with the quote character ” are called quoted-fields:
normal string,"quoted-field"
Within a quoted-field, a quote character followed by a second quote character is considered a single quote:
"the ""word"" is true","a ""quoted-field"""
Results in:
{`the "word" is true`, `a "quoted-field"`}

Multiline Fields

Newlines and commas may be included in a quoted-field:
"Multi-line
field","comma is ,"
Results in:
{`Multi-line
field`, `comma is ,`}

Build docs developers (and LLMs) love