csv

package module
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 5, 2020 License: MIT Imports: 7 Imported by: 0

README

csv

A CSV marshaling and unmarshaling library for Go, with a similar API to encoding/json

Usage

To install, run go get github.com/conradludgate/csv

Unmarshal

Takes in CSV data with a header row and will unmarshal it into a slice of a struct that matches the headers

Example:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/conradludgate/csv"
)

// Data is the column structure for the CSV.
// Most built in types have a sensible decode function from string
// Custom header names can be specified using the `csv` tag
// Custom types are supported if they implement `csv.UnmarshalCSV`
type Data struct {
	Foo    string
	Bar    int64 `csv:"bar"`
	Time   time.Time
	Custom Custom
}

type Custom struct {
	A string
	B int
}

// Example custom unmarshal function
// Turns "A|B" into Custom{A, B}
func (c *Custom) UnmarshalCSV(value string) error {
	split := strings.Split(value, "|")
	if len(split) != 2 {
		return fmt.Errorf("invalid data for custom decode")
	}
	c.A = split[0]
	var err error
	c.B, err = strconv.Atoi(split[1])
	return err
}

func example1() {
	// Input data with the first row being a header row
	data := []byte(`Foo,bar,Time,Custom
hello world,9223372036854775807,2006-01-02T15:04:05-07:00,value1|1
goodbye world,-9223372036854775808,2020-07-03T16:39:44+01:00,value2|2`)

	// Create a slice of the column data
	output := []Data{}
	// Unmarshal just like with `json.Unmarshal`
	err := csv.Unmarshal(data, &output)
	if err != nil {
		panic(err)
	}

	outputJSON, _ := json.MarshalIndent(output, "", "\t")
	fmt.Println(string(outputJSON))
	// Outputs:
	// [
	//     {
	//         "Foo": "hello world",
	//         "Bar": 9223372036854775807,
	//         "Time": "2006-01-02T15:04:05-07:00",
	//         "Custom": {
	//             "A": "value1",
	//             "B": 1
	//         }
	//     },
	//     {
	//         "Foo": "goodbye world",
	//         "Bar": -9223372036854775808,
	//         "Time": "2020-07-03T16:39:44+01:00",
	//         "Custom": {
	//             "A": "value2",
	//             "B": 2
	//         }
	//     }
	// ]
}

func example2() {
	data := bytes.NewReader([]byte(`Foo,bar,Time,Custom
hello world,9223372036854775807,2006-01-02T15:04:05-07:00,value1|1
goodbye world,-9223372036854775808,2020-07-03T16:39:44+01:00,value2|2`))

	// Also supports `io.Reader`
	decoder := csv.NewDecoder(data)
	output := []Data{}
	err := decoder.Unmarshal(&data)
	if err != nil {
		panic(err)
	}
}
Marshal

Marshal works in a very similar but the opposite way. Takes in a slice or array of a struct, writes the header row of all the fields then proceeds to write the contents of the slice as CSV data.

TODO:

More tests need to be written, testing the usage of the built in CSV library's decoding/encoding capabilities.

Support more of the stdlib's types for marshalling and unmarshalling.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Marshal

func Marshal(v interface{}) ([]byte, error)

Marshal the value v into a csv

Example
package main

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/conradludgate/csv"
)

type Data struct {
	Foo    string
	Bar    int64 `csv:"bar"`
	Time   time.Time
	Custom Custom
}

type Custom struct {
	A string
	B int
}

func (c *Custom) UnmarshalCSV(value string) error {
	split := strings.Split(value, "|")
	if len(split) != 2 {
		return fmt.Errorf("invalid data for custom decode")
	}
	c.A = split[0]
	var err error
	c.B, err = strconv.Atoi(split[1])
	return err
}

func (c Custom) MarshalCSV() string {
	return fmt.Sprintf("%s|%d", c.A, c.B)
}

func main() {
	time1 := time.Date(2006, 01, 02, 15, 04, 05, 0, time.FixedZone("MST", -7*60*60))
	time2 := time.Date(2020, 07, 03, 16, 39, 44, 0, time.FixedZone("BST", 1*60*60))

	data := []Data{
		{
			Foo:  "hello world",
			Bar:  9223372036854775807,
			Time: time1,
			Custom: Custom{
				A: "value1",
				B: 1,
			},
		},
		{
			Foo:  "goodbye world",
			Bar:  -9223372036854775808,
			Time: time2,
			Custom: Custom{
				A: "value2",
				B: 2,
			},
		},
	}

	bytes, _ := csv.Marshal(data)
	fmt.Println(string(bytes))
}
Output:

Foo,bar,Time,Custom
hello world,9223372036854775807,2006-01-02T15:04:05-07:00,value1|1
goodbye world,-9223372036854775808,2020-07-03T16:39:44+01:00,value2|2

func Unmarshal

func Unmarshal(data []byte, v interface{}) error

Unmarshal the byte slice as a csv into the value v v must be an array of structs, where the struct field names (or tags) define the csv header name to decode from Will decode most built in types automatically, otherwise it will use the FromString interface to decode

Example
package main

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/conradludgate/csv"
)

type Data struct {
	Foo    string
	Bar    int64 `csv:"bar"`
	Time   time.Time
	Custom Custom
}

type Custom struct {
	A string
	B int
}

func (c *Custom) UnmarshalCSV(value string) error {
	split := strings.Split(value, "|")
	if len(split) != 2 {
		return fmt.Errorf("invalid data for custom decode")
	}
	c.A = split[0]
	var err error
	c.B, err = strconv.Atoi(split[1])
	return err
}

func (c Custom) MarshalCSV() string {
	return fmt.Sprintf("%s|%d", c.A, c.B)
}

func main() {
	data := []byte(`Foo,bar,Time,Custom
hello world,9223372036854775807,2006-01-02T15:04:05-07:00,value1|1
goodbye world,-9223372036854775808,2020-07-03T16:39:44+01:00,value2|2`)

	output := []Data{}
	err := csv.Unmarshal(data, &output)
	if err != nil {
		panic(err)
	}

	outputJSON, _ := json.MarshalIndent(output, "", "\t")
	fmt.Println(string(outputJSON))
}
Output:

[
	{
		"Foo": "hello world",
		"Bar": 9223372036854775807,
		"Time": "2006-01-02T15:04:05-07:00",
		"Custom": {
			"A": "value1",
			"B": 1
		}
	},
	{
		"Foo": "goodbye world",
		"Bar": -9223372036854775808,
		"Time": "2020-07-03T16:39:44+01:00",
		"Custom": {
			"A": "value2",
			"B": 2
		}
	}
]

Types

type Decoder

type Decoder struct {
	// contains filtered or unexported fields
}

Decoder reads and decodes a csv into an array from an input stream

func NewDecoder

func NewDecoder(r io.Reader) *Decoder

NewDecoder creates a new decoder from the given reader

func (*Decoder) Decode

func (d *Decoder) Decode(v interface{}) error

Decode decodes the reader into the value v v must be an array of structs, where the struct field names (or tags) define the csv header name to decode from Will decode most built in types, otherwise it will use the FromString interface to decode

Example
package main

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/conradludgate/csv"
)

type Data struct {
	Foo    string
	Bar    int64 `csv:"bar"`
	Time   time.Time
	Custom Custom
}

type Custom struct {
	A string
	B int
}

func (c *Custom) UnmarshalCSV(value string) error {
	split := strings.Split(value, "|")
	if len(split) != 2 {
		return fmt.Errorf("invalid data for custom decode")
	}
	c.A = split[0]
	var err error
	c.B, err = strconv.Atoi(split[1])
	return err
}

func (c Custom) MarshalCSV() string {
	return fmt.Sprintf("%s|%d", c.A, c.B)
}

func main() {
	data := strings.NewReader(`Foo,bar,Time,Custom
hello world,9223372036854775807,2006-01-02T15:04:05-07:00,value1|1
goodbye world,-9223372036854775808,2020-07-03T16:39:44+01:00,value2|2`)

	decoder := csv.NewDecoder(data)
	output := []Data{}
	err := decoder.Decode(&output)
	if err != nil {
		panic(err)
	}

	outputJSON, _ := json.MarshalIndent(output, "", "\t")
	fmt.Println(string(outputJSON))
}
Output:

[
	{
		"Foo": "hello world",
		"Bar": 9223372036854775807,
		"Time": "2006-01-02T15:04:05-07:00",
		"Custom": {
			"A": "value1",
			"B": 1
		}
	},
	{
		"Foo": "goodbye world",
		"Bar": -9223372036854775808,
		"Time": "2020-07-03T16:39:44+01:00",
		"Custom": {
			"A": "value2",
			"B": 2
		}
	}
]

func (*Decoder) DisableTrimLeadingSpace

func (d *Decoder) DisableTrimLeadingSpace()

DisableTrimLeadingSpace sets the value for the underlying reader to false. If TrimLeadingSpace is true, leading white space in a field is ignored. This is done even if the field delimiter is white space.

Example
package main

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/conradludgate/csv"
)

type Data struct {
	Foo    string
	Bar    int64 `csv:"bar"`
	Time   time.Time
	Custom Custom
}

type Custom struct {
	A string
	B int
}

func (c *Custom) UnmarshalCSV(value string) error {
	split := strings.Split(value, "|")
	if len(split) != 2 {
		return fmt.Errorf("invalid data for custom decode")
	}
	c.A = split[0]
	var err error
	c.B, err = strconv.Atoi(split[1])
	return err
}

func (c Custom) MarshalCSV() string {
	return fmt.Sprintf("%s|%d", c.A, c.B)
}

func main() {
	data := strings.NewReader(`Foo,bar,Time,Custom
  hello world,9223372036854775807,2006-01-02T15:04:05-07:00,value1|1
goodbye world,-9223372036854775808,2020-07-03T16:39:44+01:00,value2|2`)

	decoder := csv.NewDecoder(data)
	decoder.DisableTrimLeadingSpace()
	output := []Data{}
	err := decoder.Decode(&output)
	if err != nil {
		panic(err)
	}

	outputJSON, _ := json.MarshalIndent(output, "", "\t")
	fmt.Println(string(outputJSON))
}
Output:

[
	{
		"Foo": "  hello world",
		"Bar": 9223372036854775807,
		"Time": "2006-01-02T15:04:05-07:00",
		"Custom": {
			"A": "value1",
			"B": 1
		}
	},
	{
		"Foo": "goodbye world",
		"Bar": -9223372036854775808,
		"Time": "2020-07-03T16:39:44+01:00",
		"Custom": {
			"A": "value2",
			"B": 2
		}
	}
]

func (*Decoder) SetComment

func (d *Decoder) SetComment(c rune)

SetComment character for the csv reader

Example
package main

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/conradludgate/csv"
)

type Data struct {
	Foo    string
	Bar    int64 `csv:"bar"`
	Time   time.Time
	Custom Custom
}

type Custom struct {
	A string
	B int
}

func (c *Custom) UnmarshalCSV(value string) error {
	split := strings.Split(value, "|")
	if len(split) != 2 {
		return fmt.Errorf("invalid data for custom decode")
	}
	c.A = split[0]
	var err error
	c.B, err = strconv.Atoi(split[1])
	return err
}

func (c Custom) MarshalCSV() string {
	return fmt.Sprintf("%s|%d", c.A, c.B)
}

func main() {
	data := strings.NewReader(`Foo,bar,Time,Custom
hello world,9223372036854775807,2006-01-02T15:04:05-07:00,value1|1
# This line is a comment and will be ignored
goodbye world,-9223372036854775808,2020-07-03T16:39:44+01:00,value2|2`)

	decoder := csv.NewDecoder(data)
	decoder.SetComment('#')
	output := []Data{}
	err := decoder.Decode(&output)
	if err != nil {
		panic(err)
	}

	outputJSON, _ := json.MarshalIndent(output, "", "\t")
	fmt.Println(string(outputJSON))
}
Output:

[
	{
		"Foo": "hello world",
		"Bar": 9223372036854775807,
		"Time": "2006-01-02T15:04:05-07:00",
		"Custom": {
			"A": "value1",
			"B": 1
		}
	},
	{
		"Foo": "goodbye world",
		"Bar": -9223372036854775808,
		"Time": "2020-07-03T16:39:44+01:00",
		"Custom": {
			"A": "value2",
			"B": 2
		}
	}
]

func (*Decoder) SetDelimiter

func (d *Decoder) SetDelimiter(delim rune)

SetDelimiter character for the csv reader

Example
package main

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/conradludgate/csv"
)

type Data struct {
	Foo    string
	Bar    int64 `csv:"bar"`
	Time   time.Time
	Custom Custom
}

type Custom struct {
	A string
	B int
}

func (c *Custom) UnmarshalCSV(value string) error {
	split := strings.Split(value, "|")
	if len(split) != 2 {
		return fmt.Errorf("invalid data for custom decode")
	}
	c.A = split[0]
	var err error
	c.B, err = strconv.Atoi(split[1])
	return err
}

func (c Custom) MarshalCSV() string {
	return fmt.Sprintf("%s|%d", c.A, c.B)
}

func main() {
	data := strings.NewReader(`Foo~bar~Time~Custom
hello world~9223372036854775807~2006-01-02T15:04:05-07:00~value1|1
goodbye world~-9223372036854775808~2020-07-03T16:39:44+01:00~value2|2`)

	decoder := csv.NewDecoder(data)
	decoder.SetDelimiter('~')
	output := []Data{}
	err := decoder.Decode(&output)
	if err != nil {
		panic(err)
	}

	outputJSON, _ := json.MarshalIndent(output, "", "\t")
	fmt.Println(string(outputJSON))
}
Output:

[
	{
		"Foo": "hello world",
		"Bar": 9223372036854775807,
		"Time": "2006-01-02T15:04:05-07:00",
		"Custom": {
			"A": "value1",
			"B": 1
		}
	},
	{
		"Foo": "goodbye world",
		"Bar": -9223372036854775808,
		"Time": "2020-07-03T16:39:44+01:00",
		"Custom": {
			"A": "value2",
			"B": 2
		}
	}
]

func (*Decoder) TrimLeadingSpace

func (d *Decoder) TrimLeadingSpace()

TrimLeadingSpace the value for the underlying reader to true. If TrimLeadingSpace is true, leading white space in a field is ignored. This is done even if the field delimiter is white space.

Example
package main

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/conradludgate/csv"
)

type Data struct {
	Foo    string
	Bar    int64 `csv:"bar"`
	Time   time.Time
	Custom Custom
}

type Custom struct {
	A string
	B int
}

func (c *Custom) UnmarshalCSV(value string) error {
	split := strings.Split(value, "|")
	if len(split) != 2 {
		return fmt.Errorf("invalid data for custom decode")
	}
	c.A = split[0]
	var err error
	c.B, err = strconv.Atoi(split[1])
	return err
}

func (c Custom) MarshalCSV() string {
	return fmt.Sprintf("%s|%d", c.A, c.B)
}

func main() {
	data := strings.NewReader(`          Foo,                 bar,                     Time,  Custom
  hello world, 9223372036854775807,2006-01-02T15:04:05-07:00,value1|1
goodbye world,-9223372036854775808,2020-07-03T16:39:44+01:00,value2|2`)

	decoder := csv.NewDecoder(data)
	decoder.TrimLeadingSpace()
	output := []Data{}
	err := decoder.Decode(&output)
	if err != nil {
		panic(err)
	}

	outputJSON, _ := json.MarshalIndent(output, "", "\t")
	fmt.Println(string(outputJSON))
}
Output:

[
	{
		"Foo": "hello world",
		"Bar": 9223372036854775807,
		"Time": "2006-01-02T15:04:05-07:00",
		"Custom": {
			"A": "value1",
			"B": 1
		}
	},
	{
		"Foo": "goodbye world",
		"Bar": -9223372036854775808,
		"Time": "2020-07-03T16:39:44+01:00",
		"Custom": {
			"A": "value2",
			"B": 2
		}
	}
]

type Encoder

type Encoder struct {
	// contains filtered or unexported fields
}

Encoder encodes and writes the contents of a slice into a csv file

func NewEncoder

func NewEncoder(w io.Writer) *Encoder

NewEncoder creates a new encoder from the given writer

func (*Encoder) Encode

func (e *Encoder) Encode(v interface{}) error

Encode and write the value of v into a csv

Example
package main

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/conradludgate/csv"
)

type Data struct {
	Foo    string
	Bar    int64 `csv:"bar"`
	Time   time.Time
	Custom Custom
}

type Custom struct {
	A string
	B int
}

func (c *Custom) UnmarshalCSV(value string) error {
	split := strings.Split(value, "|")
	if len(split) != 2 {
		return fmt.Errorf("invalid data for custom decode")
	}
	c.A = split[0]
	var err error
	c.B, err = strconv.Atoi(split[1])
	return err
}

func (c Custom) MarshalCSV() string {
	return fmt.Sprintf("%s|%d", c.A, c.B)
}

func main() {
	time1 := time.Date(2006, 01, 02, 15, 04, 05, 0, time.FixedZone("MST", -7*60*60))
	time2 := time.Date(2020, 07, 03, 16, 39, 44, 0, time.FixedZone("BST", 1*60*60))

	data := []Data{
		{
			Foo:  "hello world",
			Bar:  9223372036854775807,
			Time: time1,
			Custom: Custom{
				A: "value1",
				B: 1,
			},
		},
		{
			Foo:  "goodbye world",
			Bar:  -9223372036854775808,
			Time: time2,
			Custom: Custom{
				A: "value2",
				B: 2,
			},
		},
	}

	encoder := csv.NewEncoder(os.Stdout)
	encoder.Encode(data)
}
Output:

Foo,bar,Time,Custom
hello world,9223372036854775807,2006-01-02T15:04:05-07:00,value1|1
goodbye world,-9223372036854775808,2020-07-03T16:39:44+01:00,value2|2

func (*Encoder) NoCRLF

func (e *Encoder) NoCRLF()

NoCRLF disables CRLF line ending for the csv Writer

func (*Encoder) SetDelimiter

func (e *Encoder) SetDelimiter(delim rune)

SetDelimiter character for the csv reader

Example
package main

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/conradludgate/csv"
)

type Data struct {
	Foo    string
	Bar    int64 `csv:"bar"`
	Time   time.Time
	Custom Custom
}

type Custom struct {
	A string
	B int
}

func (c *Custom) UnmarshalCSV(value string) error {
	split := strings.Split(value, "|")
	if len(split) != 2 {
		return fmt.Errorf("invalid data for custom decode")
	}
	c.A = split[0]
	var err error
	c.B, err = strconv.Atoi(split[1])
	return err
}

func (c Custom) MarshalCSV() string {
	return fmt.Sprintf("%s|%d", c.A, c.B)
}

func main() {
	time1 := time.Date(2006, 01, 02, 15, 04, 05, 0, time.FixedZone("MST", -7*60*60))
	time2 := time.Date(2020, 07, 03, 16, 39, 44, 0, time.FixedZone("BST", 1*60*60))

	data := []Data{
		{
			Foo:  "hello world",
			Bar:  9223372036854775807,
			Time: time1,
			Custom: Custom{
				A: "value1",
				B: 1,
			},
		},
		{
			Foo:  "goodbye world",
			Bar:  -9223372036854775808,
			Time: time2,
			Custom: Custom{
				A: "value2",
				B: 2,
			},
		},
	}

	encoder := csv.NewEncoder(os.Stdout)
	encoder.SetDelimiter('~')
	encoder.Encode(data)
}
Output:

Foo~bar~Time~Custom
hello world~9223372036854775807~2006-01-02T15:04:05-07:00~value1|1
goodbye world~-9223372036854775808~2020-07-03T16:39:44+01:00~value2|2

func (*Encoder) UseCRLF

func (e *Encoder) UseCRLF()

UseCRLF enables CRLF line ending for the csv Writer

type MarshalCSV

type MarshalCSV interface {
	MarshalCSV() string
}

MarshalCSV describes how CSV should handle types that aren't strings or built in

type UnmarshalCSV

type UnmarshalCSV interface {
	UnmarshalCSV(string) error
}

UnmarshalCSV describes how CSV should handle types that aren't strings or built in

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL