csvplus

package module
v0.0.0-...-a009a30 Latest Latest
Warning

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

Go to latest
Published: Mar 8, 2023 License: MIT Imports: 10 Imported by: 0

README

csvplus

GoDoc

csvplus provides marshalling/unmarshalling of CSV data (with and without header rows) into slices of structs.

Why?

csv.NewReader().Read() only provides records as []string leaving the user to perform type conversion. Also more convenient to go to/from a slice.

Examples

Unmarshal

type Item struct {
    First  string     `csvplus:"first"`
    Second int        `csvplus:"second"`
    Third  *bool      `csvplus:"third"`
    Forth  *time.Time `csvplus:"forth" csvplusFormat:"2006-01"`
}

// The CSV data we want to unmarshal.
// If your data is in a *File (or other io.Reader), use UnmarshalReader().
data := []byte("first,second,third,forth\na,1,,2000-01\nb,2,f,")

var items []Item
err := csvplus.Unmarshal(data, &items)
if err != nil {
    panic(err)
}

fmt.Printf("%+v\n", items[0])
fmt.Printf("{First:%s Second:%d Third:%t (dereferenced) Forth:%s}\n", items[1].First, items[1].Second, *items[1].Third, items[1].Forth)
// Output:
// {First:a Second:1 Third:<nil> Forth:2000-01-01 00:00:00 +0000 UTC}
// {First:b Second:2 Third:false (dereferenced) Forth:<nil>}

Custom field unmarshalling


// YesNoBool is an example field that implements Unmarshaler, it's used in an example.
type YesNoBool bool

// UnmarshalCSV is an implementation of the Unmarshaler interface, converts a string record to a native
// value for this type.
func (ynb *YesNoBool) UnmarshalCSV(s string) error {
    if ynb == nil {
        return fmt.Errorf("cannot unmarshal into nil pointer")
    }
    switch s {
    case "yes":
        *ynb = YesNoBool(true)
        return nil
    case "no":
        *ynb = YesNoBool(false)
        return nil
    }
    return fmt.Errorf("unable to convert %s to bool", s)
}

type Item struct {
    Name      string     `csvplus:"name"`
    Seen      *YesNoBool `csvplus:"seen"`   // custom type that implements Unmarshaler
    Agreed    YesNoBool  `csvplus:"agreed"` // custom type that implements Unmarshaler
    Timestamp *time.Time `csvplus:"when" csvplusFormat:"2006-01"`
}

// The CSV data we want to unmarshal, note the custom format.
data := []byte("name,seen,agreed,when\nRob,yes,yes,1999-11\nRuss,,no,")

var items []Item
err := csvplus.Unmarshal(data, &items)
if err != nil {
    panic(err)
}

for _, item := range items {
    fmt.Println(item)
}

Marshal

type Item struct {
    First  string     `csvplus:"first"`
    Second int        `csvplus:"second"`
    Third  *bool      `csvplus:"third"`
    Fourth *time.Time `csvplus:"fourth" csvplusFormat:"2006-01"`
}

tm, _ := time.Parse("2006-01", "2000-01")
f := false
items := []Item{
    {"a", 1, nil, &tm},
    {"b", 2, &f, nil},
}
data, err := csvplus.Marshal(&items)
if err != nil {
    panic(err)
}

fmt.Println(string(data))
// Output:
// first,second,third,fourth
// a,1,,2000-01
// b,2,false,

Ideas for improvement

  • csvplusNilVal tag for custom nil values (eg '-', 'n/a')
  • csvplusTrueVal & csvplusFalseVal (eg 'yes' and 'no' without custom types that implement Marshaler/Unmarshaler interfaces)
  • csvplusFormat to also handle floats via string formatting (eg %.3e)

PRs welcome.

Docs

https://godoc.org/github.com/j0hnsmith/csvplus

Documentation

Overview

Package csvplus marshals/unmarshals CSV data directly from/to slice of structs, types are converted to those matching the fields on the struct. Layout strings can be provided via struct tags for time.Time fields.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Marshal

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

Marshal marshals v into csv data.

Example
package main

import (
	"fmt"
	"time"

	"github.com/j0hnsmith/csvplus"
)

func main() {
	type Item struct {
		First  string     `csvplus:"first"`
		Second int        `csvplus:"second"`
		Third  *bool      `csvplus:"third"`
		Fourth *time.Time `csvplus:"fourth" csvplusFormat:"2006-01"`
	}

	tm, _ := time.Parse("2006-01", "2000-01")
	f := false
	items := []Item{
		{"a", 1, nil, &tm},
		{"b", 2, &f, nil},
	}
	data, err := csvplus.Marshal(&items)
	if err != nil {
		panic(err)
	}

	fmt.Println(string(data))
}
Output:

first,second,third,fourth
a,1,,2000-01
b,2,false,

func MarshalWithoutHeader

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

MarshalWithoutHeader writes csv data without a header row.

func MarshalWriter

func MarshalWriter(v interface{}, w io.Writer) error

MarshalWriter marshals v into the given writer.

func Unmarshal

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

Unmarshal parses the csv encoded data and stores the result in the slice pointed to by v. The number of records in row of the csv data must match the number of exported fields in the struct. For common types (eg int, bool, float64...) a standard conversion from a string is applied. If a type implements the Unmarshaler interface, that will be used to unmarshal the record instead. This function assumes the csv data has a header row (which is skipped), see the Decoder type if your data doesn't have a header row.

Example
package main

import (
	"fmt"
	"time"

	"github.com/j0hnsmith/csvplus"
)

func main() {
	type Item struct {
		First  string     `csvplus:"first"`
		Second int        `csvplus:"second"`
		Third  *bool      `csvplus:"third"`
		Forth  *time.Time `csvplus:"forth" csvplusFormat:"2006-01"`
	}

	// The CSV data we want to unmarshal.
	// If your data is in a *File (or other io.Reader), use UnmarshalReader().
	data := []byte("first,second,third,forth\na,1,,2000-01\nb,2,f,")

	var items []Item
	err := csvplus.Unmarshal(data, &items)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%+v\n", items[0])
	fmt.Printf("{First:%s Second:%d Third:%t (dereferenced) Forth:%s}\n", items[1].First, items[1].Second, *items[1].Third, items[1].Forth)
}
Output:

{First:a Second:1 Third:<nil> Forth:2000-01-01 00:00:00 +0000 UTC}
{First:b Second:2 Third:false (dereferenced) Forth:<nil>}

func UnmarshalReader

func UnmarshalReader(r io.Reader, v interface{}) error

UnmarshalReader is the same as Unmarshal but takes it's input data from an io.Reader.

func UnmarshalWithoutHeader

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

UnmarshalWithoutHeader is used to unmarshal csv data that doesn't have a header row.

Types

type Decoder

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

A Decoder reads and decodes CSV records from an input stream. Useful if your data doesn't have a header row.

func NewDecoder

func NewDecoder(r io.Reader) *Decoder

NewDecoder reads and decodes CSV records from r.

func (*Decoder) Decode

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

Decode reads reads csv recorder into v.

func (*Decoder) SetCSVReader

func (dec *Decoder) SetCSVReader(r *csv.Reader) *Decoder

SetCSVReader allows for using a custom csv.Reader (eg | field separator instead of ,).

Example
package main

import (
	"bytes"
	"encoding/csv"
	"fmt"
	"time"

	"github.com/j0hnsmith/csvplus"
)

func main() {
	type Item struct {
		Name      string     `csvplus:"name"`
		Timestamp *time.Time `csvplus:"when" csvplusFormat:"2006-01"`
	}

	data := []byte("name|when\nRob|1999-11\nRuss|")

	// create a *csv.Reader
	r := csv.NewReader(bytes.NewReader(data))
	// modify to get the required functionality, in this case '|' separated fields
	r.Comma = '|'

	dec := csvplus.NewDecoder(bytes.NewReader(data))
	// set the csv reader to the custom reader
	dec.SetCSVReader(r)

	var items []Item
	err := dec.Decode(&items)
	if err != nil {
		panic(err)
	}

	fmt.Printf("{%s %s}\n", items[0].Name, items[0].Timestamp)
	fmt.Printf("{%s %+v}\n", items[1].Name, items[1].Timestamp)
}
Output:

{Rob 1999-11-01 00:00:00 +0000 UTC}
{Russ <nil>}

func (*Decoder) UseHeader

func (dec *Decoder) UseHeader(b bool) *Decoder

UseHeader sets whether the first data row is a header row.

type Encoder

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

An Encoder writes csv data from a list of struct.

func NewEncoder

func NewEncoder(w io.Writer) *Encoder

NewEncoder returns an initialised Encoder.

func (*Encoder) Encode

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

Encode encodes v into csv data.

func (*Encoder) SetCSVWriter

func (enc *Encoder) SetCSVWriter(r *csv.Writer) *Encoder

SetCSVWriter allows for using a csv.Writer with custom config (eg | field separator instead of ,).

func (*Encoder) UseHeader

func (enc *Encoder) UseHeader(v bool) *Encoder

UseHeader sets whether to add a header row to the csv data.

type Marshaler

type Marshaler interface {
	MarshalCSV() ([]byte, error)
}

Marshaler is the interface implemented by types that can marshal a csv value (string) of themselves.

type UnmarhsalError

type UnmarhsalError struct {
	Column string
	Row    int
	Value  string
	RawErr error
}

func (UnmarhsalError) Error

func (um UnmarhsalError) Error() string

type Unmarshaler

type Unmarshaler interface {
	UnmarshalCSV(string) error
}

Unmarshaler is the interface implemented by types that can unmarshal a csv record of themselves.

Example
package main

import (
	"fmt"
	"time"

	"github.com/j0hnsmith/csvplus"
)

// YesNoBool is an example field that implements Unmarhsaler, it's used in an example.
type YesNoBool bool

// UnmarshalCSV is an implementation of the Unmarshaller interface, converts a string record to a native
// value for this type.
func (ynb *YesNoBool) UnmarshalCSV(s string) error {
	if ynb == nil {
		return fmt.Errorf("cannot unmarshal into nil pointer")
	}
	switch s {
	case "yes":
		*ynb = YesNoBool(true)
		return nil
	case "no":
		*ynb = YesNoBool(false)
		return nil
	case "":
		*ynb = YesNoBool(false)
		return nil
	}
	return fmt.Errorf("unable to convert %s to bool", s)
}

func (ynb *YesNoBool) MarshalCSV() ([]byte, error) {
	if ynb == nil || !*ynb {
		return []byte("no"), nil
	}
	return []byte("yes"), nil
}

func main() {
	//	type YesNoBool bool

	// 	func (ynb *YesNoBool) UnmarshalCSV(s string) error {
	// 		if ynb == nil {
	// 			return fmt.Errorf("cannot unmarshal into nil pointer")
	// 		}
	// 		switch s {
	// 		case "yes":
	// 			*ynb = YesNoBool(true)
	// 			return nil
	// 		case "no":
	// 			*ynb = YesNoBool(false)
	// 			return nil
	//		case "":
	//			*ynb = YesNoBool(false) // custom zero value
	//			return nil
	// 		}
	// 			return fmt.Errorf("unable to convert %s to bool", s)
	// 		}

	type Item struct {
		Name      string     `csvplus:"name"`
		Seen      *YesNoBool `csvplus:"seen"`   // custom type that implements Unmarshaler
		Agreed    *YesNoBool `csvplus:"agreed"` // custom type that implements Unmarshaler
		Timestamp *time.Time `csvplus:"when" csvplusFormat:"2006-01"`
	}

	// The CSV data we want to unmarshal, note the custom format.
	data := []byte("name,seen,agreed,when\nRob,yes,yes,1999-11\nRuss,,no,")

	var items []Item
	err := csvplus.Unmarshal(data, &items)
	if err != nil {
		panic(err)
	}

	fmt.Printf("{%s %t (dereferenced) %t %s}\n", items[0].Name, *items[0].Seen, *items[0].Agreed, items[0].Timestamp)
	fmt.Printf("{%s %+v %t %+v}\n", items[1].Name, *items[1].Seen, *items[1].Agreed, items[1].Timestamp)
}
Output:

{Rob true (dereferenced) true 1999-11-01 00:00:00 +0000 UTC}
{Russ false false <nil>}

Jump to

Keyboard shortcuts

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