Repository: influxdata/influxdb-client-go
Pull request #240 (2021-04-02 06:35:47)

feat(Query): Adding Reader.Decode
## Proposed Changes
- Added `Reader.Decode(x interface{}) errror` to convert CSV values to a struct fields or a slice
- `Reader.NextRow()` doesn't convert values 
- `Reader.Row()` returns raw CSV data

## Checklist

<!-- Checkboxes below this note can be erased if not applicable to your Pull Request. -->

- [ ] CHANGELOG.md updated
- [X] Rebased/mergeable
- [X] A test has been added if appropriate
- [X] Tests pass
- [X] Commit messages are in [semantic format](https://seesparkbox.com/foundry/semantic_commit_messages)
- [X] Sign [CLA](https://influxdata.com/community/cla/) (if not already signed)


vlastahajek: @rogger, I've pushed an update that addresses all remaining issues. Please, kindly check.

Patch:
diff --git a/README.md b/README.md
index 01121aa..cc9c301 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ State: under development.
 |:----------|:----------|:----------|
 | `WritePoints()` | `/api/v2/write` |  | 
 | `PointWriter` | `/api/v2/write` |  |
-| `Query()` | `/api/v2/query` |  |
+| `Query()` | `/api/v2/query` | In progress |
 | `DeletePoints()` | `/api/v2/delete` |  |
 | `Ready()` | `/ready` |  |
 | `Health()` | `/health` |  |
diff --git a/TODO.md b/TODO.md
index dcb392a..a83f150 100644
--- a/TODO.md
+++ b/TODO.md
@@ -399,13 +399,6 @@ type Filter struct {
 	AfterTime time.Time
 }
 
-// Decode decodes the current row into x, which should be
-// a pointer to a struct. Columns in the row are decoded into
-// appropriate fields in the struct, using the tag conventions
-// described by encoding/json to determine how to map
-// column names to struct fields.
-func (r *QueryResult) Decode(x interface{}) error
-
 // PointWriter implements a batching point writer.
 type PointWriter struct {
 	// unexported fields
diff --git a/annotatedcsv/decode.go b/annotatedcsv/decode.go
new file mode 100644
index 0000000..d11ab22
--- /dev/null
+++ b/annotatedcsv/decode.go
@@ -0,0 +1,459 @@
+// Copyright 2021 InfluxData, Inc. All rights reserved.
+// Use of this source code is governed by MIT
+// license that can be found in the LICENSE file.
+
+package annotatedcsv
+
+import (
+	"encoding/base64"
+	"fmt"
+	"reflect"
+	"strconv"
+	"strings"
+	"time"
+
+	ireflect "github.com/influxdata/influxdb-client-go/internal/reflect"
+)
+
+// Decode decodes the current row into x, which should be
+// a pointer to a struct or a pointer to a slice.
+//
+// When it's a pointer to a struct, columns in the row are decoded into
+// appropriate fields in the struct, using the similar tag conventions
+// described by encoding/json to determine how to map
+// column names to struct fields. Tag prefix must start with "csv:":
+//
+//  type Point struct {
+//      Timestamp  time.Time `csv:"_time"`
+//      Value      float32   `csv:"_value"`
+//      Location   string    `csv:"location"`
+//      Sensor     string    `csv:"type"`
+//  }
+//
+//  var p Point
+//  err := r.Decode(&p)
+//
+// When it's a pointer to a slice, the slice is changed
+// to have one element for each column (reusing
+// space in the slice if possible), and each element is
+// set to the value in the corresponding column.
+//
+// When decoding into an empty interface value, the resulting
+// type depends on the column type:
+//
+// - string, tag or unrecognized: string
+// - double: float64
+// - unsignedLong: uint64
+// - long: int64
+// - boolean: bool
+// - duration: time.Duration
+// - dateTime: time.Time
+//
+// Any value can be decoded into a string without
+// error - the result is the value in the CSV, so
+//
+//     var row []string
+//     r.Decode(&row)
+//
+// will always succeed and provide all the values in the column as strings.
+//
+// Similarly, any row can be decoded into []interface{}, which will convert
+// all values into elements of the slice according to the rules for empty interfaces above.
+//
+// Slice capacity will be reused when possible.
+func (r *Reader) Decode(x interface{}) error {
+	t := reflect.TypeOf(x)
+	if err := r.initColumns(t, r.cols); err != nil {
+		return err
+	}
+	return r.decodeRow(reflect.ValueOf(x), r.row)
+}
+
+// initColumns initializes r.colSetters for decoding the given columns
+// into a value of the given type.
+func (r *Reader) initColumns(t reflect.Type, columns []Column) error {
+	if t == r.decodeType {
+		return nil
+	}
+	if t.Kind() != reflect.Ptr {
+		return fmt.Errorf("cannot decode into non-pointer type %v", t)
+	}
+	et := t.Elem()
+	if et.Kind() != reflect.Struct && et.Kind() != reflect.Slice {
+		return fmt.Errorf("cannot decode into %v", t)
+	}
+	setters := make([]fieldSetter, len(columns))
+	switch et.Kind() {
+	case reflect.Struct:
+		fieldsMap := make(map[string]reflect.StructField)
+		fields := ireflect.VisibleFields(et)
+		for _, f := range fields {
+			name := f.Name
+			if tag, ok := f.Tag.Lookup("csv"); ok {
+				if tag == "-" {
+					continue
+				}
+				if i := strings.Index(tag, ","); i >= 0 {
+					if i != len(tag)-1 {
+						return fmt.Errorf("tag attributes are not supported")
+					}
+					tag = tag[:i]
+				}
+				name = tag
+			}
+			fieldsMap[name] = f
+		}
+
+		for i, col := range columns {
+			f, ok := fieldsMap[col.Name]
+			if !ok {
+				// The column isn't mentioned in the struct.
+				continue
+			}
+			ftype := f.Type
+			colType, ok := columnTypes[col.Type]
+			if !ok {
+				// ignore invalid type and use string
+				colType = stringCol
+			}
+			convert, ok := conversions[conv{colType, fieldKindOf(ftype)}]
+			if !ok {
+				return fmt.Errorf("cannot convert from column type %s to %v", col.Type, ftype)
+			}
+			setters[i] = func(v reflect.Value, colIndex int) error {
+				return r.convertColumnValue(v.FieldByIndex(f.Index), colIndex, convert)
+			}
+		}
+		r.decodeRow = func(v reflect.Value, row []string) error {
+			if v.IsNil() {
+				return fmt.Errorf("decode into nil %s", v.Type())
+			}
+			v = v.Elem()
+			for i, c := range r.colSetters {
+				if c == nil {
+					continue
+				}
+				if err := c(v, i); err != nil {
+					return err
+				}
+			}
+			return nil
+		}
+	case reflect.Slice:
+		s := et.Elem()
+		if s != stringType && s != emptyInterfaceType {
+			return fmt.Errorf("cannot decode into *[]%v", s)
+		}
+		fkind := fieldKindOf(s)
+		for i, col := range columns {
+			colType, ok := columnTypes[col.Type]
+			if !ok {
+				// ignore invalid type and use string
+				colType = stringCol
+			}
+
+			convert, ok := conversions[conv{colType, fkind}]
+			if !ok {
+				return fmt.Errorf("cannot convert from column type %s to %v", col.Type, s)
+			}
+			setters[i] = func(v reflect.Value, colIndex int) error {
+				if err := r.convertColumnValue(v.Index(colIndex), colIndex, convert); err != nil {
+					return err
+				}
+				return nil
+			}
+		}
+		r.decodeRow = func(v reflect.Value, row []string) error {
+			if v.IsNil() {
+				return fmt.Errorf("decode into nil %s", v.Type())
+			}
+			e := v.Elem()
+			c := len(r.colSetters)
+			if e.Cap() < c {
+				e = reflect.MakeSlice(e.Type(), c, c)
+			} else {
+				e = e.Slice(0, c)
+			}
+			for i, setf := range r.colSetters {
+				if err := setf(e, i); err != nil {
+					return err
+				}
+			}
+			v.Elem().Set(e)
+			return nil
+		}
+	}
+
+	r.colSetters = setters
+	r.decodeType = t
+
+	return nil
+}
+
+// convertColumnValue set a value from current row of given column index
+// to a struct or a slice field value
+func (r *Reader) convertColumnValue(v reflect.Value, colIndex int, convert valueSetter) error {
+	if err := convert(v, r.row[colIndex]); err != nil {
+		return fmt.Errorf(`cannot convert value %q in column of type %q to Go type %v at line %d: %w`, r.row[colIndex], r.cols[colIndex].Type, v.Type(), r.r.Line(), err)
+	}
+	return nil
+}
+
+// fieldKind represents one of the possible kinds of struct field.
+// It's similar to reflect.Kind (every reflect.Kind constant i
+// is represented as fieldKind(i)) except that it also has
+// defined constants for field types that have special treatment,
+// such as time.Duration.
+type fieldKind uint
+
+// conv represents a possible conversion source and destination.
+type conv struct {
+	from colType
+	to   fieldKind
+}
+
+// intKinds holds all supported signed integer kinds
+var intKinds = []fieldKind{
+	fieldKind(reflect.Int),
+	fieldKind(reflect.Int8),
+	fieldKind(reflect.Int16),
+	fieldKind(reflect.Int32),
+	fieldKind(reflect.Int64),
+}
+
+// uintKinds holds all supported unsigned integer kinds
+var uintKinds = []fieldKind{
+	fieldKind(reflect.Uint),
+	fieldKind(reflect.Uint8),
+	fieldKind(reflect.Uint16),
+	fieldKind(reflect.Uint32),
+	fieldKind(reflect.Uint64),
+}
+
+// floatKinds holds all supported floating point number kinds
+var floatKinds = []fieldKind{
+	fieldKind(reflect.Float32),
+	fieldKind(reflect.Float64),
+}
+
+// rest of supported type kinds
+const (
+	durationKind = fieldKind(255 + iota)
+	timeKind
+	bytesKind
+)
+
+// colType represents the type of an annotated CSV column.
+type colType uint
+
+const (
+	stringCol = colType(iota)
+	boolCol
+	durationCol
+	longCol
+	unsignedLongCol
+	doubleCol
+	base64BinaryCol
+	rfc3339Col
+)
+
+// columnTypes maps annotated CSV types
+// to integer column type
+var columnTypes = map[string]colType{
+	"string":               stringCol,
+	"boolean":              boolCol,
+	"duration":             durationCol,
+	"long":                 longCol,
+	"unsignedLong":         unsignedLongCol,
+	"double":               doubleCol,
+	"base64Binary":         base64BinaryCol,
+	"dateTime:RFC3339":     rfc3339Col,
+	"dateTime:RFC3339Nano": rfc3339Col,
+	"dateTime":             rfc3339Col,
+}
+
+// conversions maps all possible conversions from column types to field kinds
+var conversions map[conv]valueSetter
+
+// stringType is the exact type for the empty string
+var stringType = reflect.TypeOf("")
+
+// emptyInterfaceType is the exact type for the empty interface{}
+var emptyInterfaceType = reflect.TypeOf(new(interface{})).Elem()
+
+// canonicalTypes holds the Go type that best represents
+// all the annotated CSV column kinds (keyed  by column type).
+var canonicalTypes = []reflect.Type{
+	stringCol:       reflect.TypeOf(""),
+	boolCol:         reflect.TypeOf(true),
+	durationCol:     reflect.TypeOf(time.Duration(1)),
+	longCol:         reflect.TypeOf(int64(0)),
+	unsignedLongCol: reflect.TypeOf(uint64(0)),
+	doubleCol:       reflect.TypeOf(0.0),
+	base64BinaryCol: reflect.TypeOf([]byte{}),
+	rfc3339Col:      reflect.TypeOf(time.Time{}),
+}
+
+// fieldKindOf determines a fieldKind for given reflect.Type
+func fieldKindOf(t reflect.Type) fieldKind {
+	switch t {
+	case canonicalTypes[durationCol]:
+		return durationKind
+	case canonicalTypes[rfc3339Col]:
+		return timeKind
+	case canonicalTypes[base64BinaryCol]:
+		return bytesKind
+	}
+	if t.Kind() == reflect.Interface && t != emptyInterfaceType {
+		// Only the empty interface type is allowed.
+		return fieldKind(reflect.Invalid)
+	}
+	return fieldKind(t.Kind())
+}
+
+// fieldSetter sets value of column from colIndex
+// to the corresponding field of v
+type fieldSetter func(v reflect.Value, colIndex int) error
+
+// valueSetter converts a string value to the value type of v
+// and sets it to v
+type valueSetter func(v reflect.Value, s string) error
+
+// rowDecoder decodes the row with the given values into v,
+// which must have type decodeType.
+type rowDecoder func(v reflect.Value, row []string) error
+
+// toInterface returns a function for setting value of given column type
+// to an interface{} field
+func toInterface(col colType) valueSetter {
+	t := canonicalTypes[col]
+	convert, ok := conversions[conv{col, fieldKindOf(t)}]
+	if !ok {
+		panic("conversion not found (should not happen)")
+	}
+	return func(v reflect.Value, s string) error {
+		e := reflect.New(t).Elem()
+		if err := convert(e, s); err != nil {
+			return err
+		}
+		v.Set(e)
+		return nil
+	}
+}
+
+// toString sets a string to a value of type string
+func toString(v reflect.Value, s string) error {
+	v.SetString(s)
+	return nil
+}
+
+// toFloat converts a string to a value of type float
+func toFloat(v reflect.Value, s string) error {
+	x, err := strconv.ParseFloat(s, 64)
+	if err != nil {
+		return err
+	}
+	if v.OverflowFloat(x) {
+		return fmt.Errorf("value %s overflows type %s", s, v.Type())
+	}
+	v.SetFloat(x)
+	return nil
+}
+
+// toBool converts a string to a value of type bool
+func toBool(v reflect.Value, s string) error {
+	var b bool
+	switch s {
+	case "true":
+		b = true
+	case "false":
+		b = false
+	default:
+		return fmt.Errorf("invalid bool value: %q", s)
+	}
+	v.SetBool(b)
+	return nil
+}
+
+// toInt converts a string to a value of type signed int
+func toInt(v reflect.Value, s string) error {
+	x, err := strconv.ParseInt(s, 10, 64)
+	if err != nil {
+		return err
+	}
+	if v.OverflowInt(x) {
+		return fmt.Errorf("value %s overflows type %s", s, v.Type())
+	}
+	v.SetInt(x)
+	return nil
+}
+
+// toUint converts a string to a value of type unsigned int
+func toUint(v reflect.Value, s string) error {
+	x, err := strconv.ParseUint(s, 10, 64)
+	if err != nil {
+		return err
+	}
+	if v.OverflowUint(x) {
+		return fmt.Errorf("value %s overflows type %s", s, v.Type())
+	}
+	v.SetUint(x)
+	return nil
+}
+
+// toTime converts a string to a time value
+func toTime(v reflect.Value, s string) error {
+	x, err := time.Parse(time.RFC3339Nano, s)
+	if err != nil {
+		return err
+	}
+	v.Set(reflect.ValueOf(x))
+	return nil
+}
+
+// toDuration converts a string to a duration value
+func toDuration(v reflect.Value, s string) error {
+	x, err := time.ParseDuration(s)
+	if err != nil {
+		return err
+	}
+	v.Set(reflect.ValueOf(x))
+	return nil
+}
+
+// toBytes decodes a base64 encoded string to a slice of bytes
+func toBytes(v reflect.Value, s string) error {
+	x, err := base64.StdEncoding.DecodeString(s)
+	if err != nil {
+		return err
+	}
+	v.Set(reflect.ValueOf(x))
+	return nil
+}
+
+func init() {
+	// initializing conversions in init() because of cycle dependency in toInterface()
+	conversions = make(map[conv]valueSetter)
+	for _, k := range intKinds {
+		conversions[conv{longCol, k}] = toInt
+		conversions[conv{unsignedLongCol, k}] = toInt
+	}
+	for _, k := range uintKinds {
+		conversions[conv{unsignedLongCol, k}] = toUint
+	}
+	for _, k := range floatKinds {
+		conversions[conv{longCol, k}] = toFloat
+		conversions[conv{unsignedLongCol, k}] = toFloat
+		conversions[conv{doubleCol, k}] = toFloat
+	}
+	conversions[conv{boolCol, fieldKind(reflect.Bool)}] = toBool
+	conversions[conv{stringCol, fieldKind(reflect.String)}] = toString
+	conversions[conv{durationCol, durationKind}] = toDuration
+	conversions[conv{base64BinaryCol, bytesKind}] = toBytes
+	conversions[conv{rfc3339Col, timeKind}] = toTime
+
+	for t := range canonicalTypes {
+		conversions[conv{colType(t), fieldKind(reflect.Interface)}] = toInterface(colType(t))
+		conversions[conv{colType(t), fieldKind(reflect.String)}] = toString
+	}
+}
diff --git a/annotatedcsv/reader.go b/annotatedcsv/reader.go
index c1c37c1..15083b2 100644
--- a/annotatedcsv/reader.go
+++ b/annotatedcsv/reader.go
@@ -30,15 +30,11 @@
 package annotatedcsv
 
 import (
-	"encoding/base64"
 	"fmt"
+	"github.com/influxdata/influxdb-client-go/internal/csv"
 	"io"
-	"math"
-	"strconv"
+	"reflect"
 	"strings"
-	"time"
-
-	"github.com/influxdata/influxdb-client-go/internal/csv"
 )
 
 // Column represents a column in one section of CSV.
@@ -48,8 +44,7 @@ type Column struct {
 	// Group specifies whether this column is a group key.
 	Group bool
 	// Default holds the default value for items in this column.
-	// It will be nil if no default is specified.
-	Default interface{}
+	Default string
 	// Type holds the type of the column.
 	Type string
 }
@@ -70,11 +65,11 @@ func NewReader(r io.Reader) *Reader {
 // Within a section, successive calls to NextRow will advance to each row.
 // If NextRow is called without first calling NextSection, the rows from the first section will be returned.
 //
-// The Row method returns all the data for a given row; ValueByName can be used
-// to get a value for a column with a particular name.
+// The Row method returns all the data for a given row in raw (string) format.
+// Use Decode to convert values to fields of a struct or slice.
 type Reader struct {
 	cols []Column
-	row  []interface{}
+	row  []string
 	err  error
 
 	hasPeeked bool
@@ -83,6 +78,16 @@ type Reader struct {
 	r         *csv.Reader
 	// columnIndexes maps column names to their indexes.
 	columnIndexes map[string]int
+	// decodeType holds the type that was last decoded into.
+	// This is reset at the start of each section.
+	decodeType reflect.Type
+	// colSetters holds an element for each column in the current
+	// section. Given an instance of decodeType, it decodes
+	// the column string value into the appropriate field.
+	colSetters []fieldSetter
+	// decodeRow is the function for decoding a CSV row
+	// into an instance of decodeType using colSetters
+	decodeRow rowDecoder
 }
 
 // NextSection advances to the next section in the csv stream and reports whether
@@ -107,6 +112,9 @@ func (r *Reader) NextSection() bool {
 		return false
 	}
 	r.cols = cols
+	// Zero the cached decode type because it probably won't be
+	// valid for the next section.
+	r.decodeType = nil
 	return true
 }
 
@@ -142,25 +150,14 @@ func (r *Reader) NextRow() bool {
 	return true
 }
 
-// Row returns the values in the current row of the current section.
+// Row returns the raw values in the current row of the current section.
 // It returns nil if there is no current row.
 // All rows in a section have the same number of values.
-func (r *Reader) Row() []interface{} {
+func (r *Reader) Row() []string {
 	return r.row
 }
 
-// ValueByName returns the value for the column with the given name.
-// It returns nil if section has no value for such column.
-func (r *Reader) ValueByName(name string) interface{} {
-	if r.columnIndexes != nil {
-		if i, ok := r.columnIndexes[name]; ok {
-			return r.row[i]
-		}
-	}
-	return nil
-}
-
-func (r *Reader) readRow() ([]interface{}, error) {
+func (r *Reader) readRow() ([]string, error) {
 	row, err := r.peek()
 	if err != nil {
 		return nil, err
@@ -174,28 +171,17 @@ func (r *Reader) readRow() ([]interface{}, error) {
 	if colsCount != len(r.cols) {
 		return nil, fmt.Errorf("inconsistent number of columns at line %d (got %d items want %d)", r.r.Line(), colsCount, len(r.cols))
 	}
-	rowVals := make([]interface{}, colsCount)
-	for i, val := range row[1:] {
-		col := r.cols[i]
-		if col.Default != nil && val == "" {
-			rowVals[i] = col.Default
-			continue
-		}
-		if val == "" && col.Name == "" {
-			continue
+	for i, v := range row[1:] {
+		if v == "" {
+			row[i+1] = r.cols[i].Default
 		}
-		x, err := convertToType(val, col.Type)
-		if err != nil {
-			return nil, fmt.Errorf("cannot convert value %q to type %q at line %d: %v", val, col.Type, r.r.Line(), err)
-		}
-		rowVals[i] = x
 	}
-	return rowVals, nil
+	return row[1:], nil
 }
 
 func (r *Reader) readHeader() ([]Column, error) {
 	var cols []Column
-	var defaults []string
+	r.columnIndexes = map[string]int{}
 	for {
 		row, err := r.read()
 		if err != nil {
@@ -207,7 +193,6 @@ func (r *Reader) readHeader() ([]Column, error) {
 		} else if colsCount != len(cols) {
 			return nil, fmt.Errorf("inconsistent table header (got %d items want %d)", colsCount, len(cols))
 		}
-		r.columnIndexes = map[string]int{}
 		if !strings.HasPrefix(row[0], "#") {
 			for i, col := range row[1:] {
 				cols[i].Name = col
@@ -225,51 +210,14 @@ func (r *Reader) readHeader() ([]Column, error) {
 				cols[i].Group = c == "true"
 			}
 		case "#default":
-			defaults = row[1:]
-		}
-	}
-	for i, d := range defaults {
-		if d == "" {
-			continue
-		}
-		x, err := convertToType(d, cols[i].Type)
-		if err != nil {
-			return nil, fmt.Errorf("cannot convert default value %q to type %q: %v", d, cols[i].Type, err)
+			for i, c := range row[1:] {
+				cols[i].Default = c
+			}
 		}
-		cols[i].Default = x
 	}
 	return cols, nil
 }
 
-func convertToType(s string, typ string) (interface{}, error) {
-	switch typ {
-	case "boolean":
-		return strconv.ParseBool(s)
-	case "long":
-		return strconv.ParseInt(s, 10, 64)
-	case "unsignedLong":
-		return strconv.ParseUint(s, 10, 64)
-	case "double":
-		x, err := strconv.ParseFloat(s, 64)
-		if err != nil {
-			return nil, err
-		}
-		if math.IsInf(x, 0) || math.IsNaN(x) {
-			return s, nil
-		}
-		return x, nil
-	case "string", "tag", "":
-		return s, nil
-	case "duration":
-		return time.ParseDuration(s)
-	case "base64Binary":
-		return base64.StdEncoding.DecodeString(s)
-	case "dateTime", "dateTime:RFC3339", "dateTime:RFC3339Nano":
-		return time.Parse(time.RFC3339, s)
-	}
-	return s, nil
-}
-
 // read consumes the next line from the CSV,
 // discarding any peeked data.
 func (r *Reader) read() ([]string, error) {
diff --git a/inluxclient/query.go b/inluxclient/query.go
index 7a8ce5c..3940380 100644
--- a/inluxclient/query.go
+++ b/inluxclient/query.go
@@ -15,13 +15,13 @@ import (
 // QueryError defines the information of Flux query error
 type QueryError struct {
 	// Message is a Flux query error message
-	Message string
+	Message string `csv:"error"`
 	// Code is an Flux query error code
-	Code int64
+	Code int64 `csv:"reference"`
 }
 
 func (e *QueryError) Error() string {
-	return fmt.Sprintf("flux query error (code %d): %s", e.Code, e.Message)
+	return fmt.Sprintf("Flux query error (code %d): %s", e.Code, e.Message)
 }
 
 // NewQueryResultReader returns new QueryResultReader for parsing Flux query result stream.
@@ -98,12 +98,14 @@ func (r *QueryResultReader) errorSection() error {
 			}
 		}
 		row := r.Row()
-		message, ok1 := row[0].(string)
-		reference, ok2 := row[1].(int64)
-		if !ok1 || !ok2 {
-			return fmt.Errorf("unexpected column types (%T, %T) in error section", row[0], row[1])
+		if row[0] == "" {
+			return errors.New("no row found in error section")
 		}
-		return &QueryError{Message: message, Code: reference}
+		var e QueryError
+		if err := r.Decode(&e); err != nil {
+			return fmt.Errorf("cannot decode row in error section: %v", err)
+		}
+		return &e
 	}
 	return nil
 }
diff --git a/internal/reflect/visiblefields.go b/internal/reflect/visiblefields.go
new file mode 100644
index 0000000..aecbd8f
--- /dev/null
+++ b/internal/reflect/visiblefields.go
@@ -0,0 +1,106 @@
+// Package reflect implements run-time reflection.
+// Note: this file copied from the Go sources master
+// When released, this can be removed.
+package reflect
+
+import "reflect"
+
+// VisibleFields returns all the visible fields in t, which must be a
+// struct type. A field is defined as visible if it's accessible
+// directly with a FieldByName call. The returned fields include fields
+// inside anonymous struct members and unexported fields. They follow
+// the same order found in the struct, with anonymous fields followed
+// immediately by their promoted fields.
+//
+// For each element e of the returned slice, the corresponding field
+// can be retrieved from a value v of type t by calling v.FieldByIndex(e.Index).
+func VisibleFields(t reflect.Type) []reflect.StructField {
+	if t == nil {
+		panic("reflect: VisibleFields(nil)")
+	}
+	if t.Kind() != reflect.Struct {
+		panic("reflect.VisibleFields of non-struct type")
+	}
+	w := &visibleFieldsWalker{
+		byName:   make(map[string]int),
+		visiting: make(map[reflect.Type]bool),
+		fields:   make([]reflect.StructField, 0, t.NumField()),
+		index:    make([]int, 0, 2),
+	}
+	w.walk(t)
+	// Remove all the fields that have been hidden.
+	// Use an in-place removal that avoids copying in
+	// the common case that there are no hidden fields.
+	j := 0
+	for i := range w.fields {
+		f := &w.fields[i]
+		if f.Name == "" {
+			continue
+		}
+		if i != j {
+			// A field has been removed. We need to shuffle
+			// all the subsequent elements up.
+			w.fields[j] = *f
+		}
+		j++
+	}
+	return w.fields[:j]
+}
+
+type visibleFieldsWalker struct {
+	byName   map[string]int
+	visiting map[reflect.Type]bool
+	fields   []reflect.StructField
+	index    []int
+}
+
+// walk walks all the fields in the struct type t, visiting
+// fields in index preorder and appending them to w.fields
+// (this maintains the required ordering).
+// Fields that have been overridden have their
+// Name field cleared.
+func (w *visibleFieldsWalker) walk(t reflect.Type) {
+	if w.visiting[t] {
+		return
+	}
+	w.visiting[t] = true
+	for i := 0; i < t.NumField(); i++ {
+		f := t.Field(i)
+		w.index = append(w.index, i)
+		add := true
+		if oldIndex, ok := w.byName[f.Name]; ok {
+			old := &w.fields[oldIndex]
+			if len(w.index) == len(old.Index) {
+				// Fields with the same name at the same depth
+				// cancel one another out. Set the field name
+				// to empty to signify that has happened, and
+				// there's no need to add this field.
+				old.Name = ""
+				add = false
+			} else if len(w.index) < len(old.Index) {
+				// The old field loses because it's deeper than the new one.
+				old.Name = ""
+			} else {
+				// The old field wins because it's shallower than the new one.
+				add = false
+			}
+		}
+		if add {
+			// Copy the index so that it's not overwritten
+			// by the other appends.
+			f.Index = append([]int(nil), w.index...)
+			w.byName[f.Name] = len(w.fields)
+			w.fields = append(w.fields, f)
+		}
+		if f.Anonymous {
+			if f.Type.Kind() == reflect.Ptr {
+				f.Type = f.Type.Elem()
+			}
+			if f.Type.Kind() == reflect.Struct {
+				w.walk(f.Type)
+			}
+		}
+		w.index = w.index[:len(w.index)-1]
+	}
+	delete(w.visiting, t)
+}


Test patch:
diff --git a/annotatedcsv/decode_test.go b/annotatedcsv/decode_test.go
new file mode 100644
index 0000000..87807e8
--- /dev/null
+++ b/annotatedcsv/decode_test.go
@@ -0,0 +1,933 @@
+package annotatedcsv
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestDecodeStruct(t *testing.T) {
+	csvTable := `#datatype,string,unsignedLong,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339Nano,duration,string,long,base64Binary,boolean
+#group,false,false,true,true,false,false,true,true,true,true
+#default,_result,,,,,,,,,
+,result,table,_start,_stop,_time,took,_field,index,note,b
+,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,32m,f,-1,ZGF0YWluYmFzZTY0,true
+,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,1h23m4s,f,1,eHh4eHhjY2NjY2NkZGRkZA==,false
+
+#datatype,long,double,dateTime,string
+#default,,,,
+,index,score,time,name
+,0,3.3,2021-02-18T10:34:08.135814545Z,Thomas
+,1,5.1,2021-02-18T22:08:44.850214724Z,John
+
+`
+	reader := strings.NewReader(csvTable)
+	res := NewReader(reader)
+	require.True(t, res.NextSection())
+	require.NoError(t, res.Err())
+	require.True(t, res.NextRow())
+	require.NoError(t, res.Err())
+
+	s := &struct {
+		Table uint          `csv:"table"`
+		Start time.Time     `csv:"_start"`
+		Stop  time.Time     `csv:"_stop"`
+		Time  time.Time     `csv:"_time"`
+		Took  time.Duration `csv:"took"`
+		Field string        `csv:"_field"`
+		Index int           `csv:"index"`
+		Note  []byte        `csv:"note"`
+		Tag   bool          `csv:"b"`
+	}{}
+	err := res.Decode(s)
+	require.NoError(t, err)
+	assert.Equal(t, &struct {
+		Table uint          `csv:"table"`
+		Start time.Time     `csv:"_start"`
+		Stop  time.Time     `csv:"_stop"`
+		Time  time.Time     `csv:"_time"`
+		Took  time.Duration `csv:"took"`
+		Field string        `csv:"_field"`
+		Index int           `csv:"index"`
+		Note  []byte        `csv:"note"`
+		Tag   bool          `csv:"b"`
+	}{
+		Table: 0,
+		Start: mustParseTime("2020-02-17T22:19:49.747562847Z"),
+		Stop:  mustParseTime("2020-02-18T22:19:49.747562847Z"),
+		Time:  mustParseTime("2020-02-18T10:34:08.135814545Z"),
+		Took:  time.Minute * 32,
+		Field: "f",
+		Index: -1,
+		Note:  []byte("datainbase64"),
+		Tag:   true,
+	}, s)
+
+	s2 := &struct {
+		Table interface{} `csv:"table"`
+		Start interface{} `csv:"_start"`
+		Stop  interface{} `csv:"_stop"`
+		Time  interface{} `csv:"_time"`
+		Took  interface{} `csv:"took"`
+		Field interface{} `csv:"_field"`
+		Index interface{} `csv:"index"`
+		Note  interface{} `csv:"note"`
+		Tag   interface{} `csv:"b"`
+	}{}
+	err = res.Decode(s2)
+	require.NoError(t, err)
+	assert.Equal(t, &struct {
+		Table interface{} `csv:"table"`
+		Start interface{} `csv:"_start"`
+		Stop  interface{} `csv:"_stop"`
+		Time  interface{} `csv:"_time"`
+		Took  interface{} `csv:"took"`
+		Field interface{} `csv:"_field"`
+		Index interface{} `csv:"index"`
+		Note  interface{} `csv:"note"`
+		Tag   interface{} `csv:"b"`
+	}{
+		Table: uint64(0),
+		Start: mustParseTime("2020-02-17T22:19:49.747562847Z"),
+		Stop:  mustParseTime("2020-02-18T22:19:49.747562847Z"),
+		Time:  mustParseTime("2020-02-18T10:34:08.135814545Z"),
+		Took:  time.Minute * 32,
+		Field: "f",
+		Index: int64(-1),
+		Note:  []byte("datainbase64"),
+		Tag:   true,
+	}, s2)
+
+	s3 := &struct {
+		Table string `csv:"table"`
+		Start string `csv:"_start"`
+		Stop  string `csv:"_stop"`
+		Time  string `csv:"_time"`
+		Took  string `csv:"took"`
+		Field string `csv:"_field"`
+		Index string `csv:"index"`
+		Note  string `csv:"note"`
+		Tag   string `csv:"b"`
+	}{}
+	err = res.Decode(s3)
+	require.NoError(t, err)
+	assert.Equal(t, &struct {
+		Table string `csv:"table"`
+		Start string `csv:"_start"`
+		Stop  string `csv:"_stop"`
+		Time  string `csv:"_time"`
+		Took  string `csv:"took"`
+		Field string `csv:"_field"`
+		Index string `csv:"index"`
+		Note  string `csv:"note"`
+		Tag   string `csv:"b"`
+	}{
+		Table: "0",
+		Start: "2020-02-17T22:19:49.747562847Z",
+		Stop:  "2020-02-18T22:19:49.747562847Z",
+		Time:  "2020-02-18T10:34:08.135814545Z",
+		Took:  "32m",
+		Field: "f",
+		Index: "-1",
+		Note:  "ZGF0YWluYmFzZTY0",
+		Tag:   "true",
+	}, s3)
+
+	require.True(t, res.NextRow(), res.Err())
+	require.NoError(t, res.Err())
+	err = res.Decode(s)
+	require.NoError(t, err)
+
+	assert.Equal(t, &struct {
+		Table uint          `csv:"table"`
+		Start time.Time     `csv:"_start"`
+		Stop  time.Time     `csv:"_stop"`
+		Time  time.Time     `csv:"_time"`
+		Took  time.Duration `csv:"took"`
+		Field string        `csv:"_field"`
+		Index int           `csv:"index"`
+		Note  []byte        `csv:"note"`
+		Tag   bool          `csv:"b"`
+	}{
+		Table: 0,
+		Start: mustParseTime("2020-02-17T22:19:49.747562847Z"),
+		Stop:  mustParseTime("2020-02-18T22:19:49.747562847Z"),
+		Time:  mustParseTime("2020-02-18T22:08:44.850214724Z"),
+		Took:  time.Hour + 23*time.Minute + 4*time.Second,
+		Field: "f",
+		Index: 1,
+		Note:  []byte("xxxxxccccccddddd"),
+		Tag:   false,
+	}, s)
+
+	err = res.Decode(s2)
+	require.NoError(t, err)
+
+	assert.Equal(t, &struct {
+		Table interface{} `csv:"table"`
+		Start interface{} `csv:"_start"`
+		Stop  interface{} `csv:"_stop"`
+		Time  interface{} `csv:"_time"`
+		Took  interface{} `csv:"took"`
+		Field interface{} `csv:"_field"`
+		Index interface{} `csv:"index"`
+		Note  interface{} `csv:"note"`
+		Tag   interface{} `csv:"b"`
+	}{
+		Table: uint64(0),
+		Start: mustParseTime("2020-02-17T22:19:49.747562847Z"),
+		Stop:  mustParseTime("2020-02-18T22:19:49.747562847Z"),
+		Time:  mustParseTime("2020-02-18T22:08:44.850214724Z"),
+		Took:  time.Hour + 23*time.Minute + 4*time.Second,
+		Field: "f",
+		Index: int64(1),
+		Note:  []byte("xxxxxccccccddddd"),
+		Tag:   false,
+	}, s2)
+
+	err = res.Decode(s3)
+	require.NoError(t, err)
+
+	assert.Equal(t, &struct {
+		Table string `csv:"table"`
+		Start string `csv:"_start"`
+		Stop  string `csv:"_stop"`
+		Time  string `csv:"_time"`
+		Took  string `csv:"took"`
+		Field string `csv:"_field"`
+		Index string `csv:"index"`
+		Note  string `csv:"note"`
+		Tag   string `csv:"b"`
+	}{
+		Table: "0",
+		Start: "2020-02-17T22:19:49.747562847Z",
+		Stop:  "2020-02-18T22:19:49.747562847Z",
+		Time:  "2020-02-18T22:08:44.850214724Z",
+		Took:  "1h23m4s",
+		Field: "f",
+		Index: "1",
+		Note:  "eHh4eHhjY2NjY2NkZGRkZA==",
+		Tag:   "false",
+	}, s3)
+
+	require.True(t, res.NextSection())
+	require.NoError(t, res.Err())
+	require.True(t, res.NextRow())
+	require.NoError(t, res.Err())
+
+	sn := &struct {
+		Index int64     `csv:"index"`
+		Time  time.Time `csv:"time"`
+		Name  string    `csv:"name"`
+		Score float64   `csv:"score"`
+		Sum   float64
+	}{}
+
+	err = res.Decode(sn)
+	require.NoError(t, err)
+
+	assert.Equal(t, &struct {
+		Index int64     `csv:"index"`
+		Time  time.Time `csv:"time"`
+		Name  string    `csv:"name"`
+		Score float64   `csv:"score"`
+		Sum   float64
+	}{
+		Index: 0,
+		Time:  mustParseTime("2021-02-18T10:34:08.135814545Z"),
+		Score: 3.3,
+		Name:  "Thomas",
+		Sum:   0,
+	}, sn)
+
+	sn2 := &struct {
+		Index interface{} `csv:"index"`
+		Time  interface{} `csv:"time"`
+		Name  interface{} `csv:"name"`
+		Score interface{} `csv:"score"`
+		Sum   interface{}
+	}{}
+
+	err = res.Decode(sn2)
+	require.NoError(t, err)
+
+	assert.Equal(t, &struct {
+		Index interface{} `csv:"index"`
+		Time  interface{} `csv:"time"`
+		Name  interface{} `csv:"name"`
+		Score interface{} `csv:"score"`
+		Sum   interface{}
+	}{
+		Index: int64(0),
+		Time:  mustParseTime("2021-02-18T10:34:08.135814545Z"),
+		Score: 3.3,
+		Name:  "Thomas",
+		Sum:   nil,
+	}, sn2)
+
+	sn3 := &struct {
+		Index string `csv:"index"`
+		Time  string `csv:"time"`
+		Name  string `csv:"name"`
+		Score string `csv:"score"`
+		Sum   string
+	}{}
+
+	err = res.Decode(sn3)
+	require.NoError(t, err)
+
+	assert.Equal(t, &struct {
+		Index string `csv:"index"`
+		Time  string `csv:"time"`
+		Name  string `csv:"name"`
+		Score string `csv:"score"`
+		Sum   string
+	}{
+		Index: "0",
+		Time:  "2021-02-18T10:34:08.135814545Z",
+		Score: "3.3",
+		Name:  "Thomas",
+		Sum:   "",
+	}, sn3)
+
+	require.True(t, res.NextRow())
+	require.NoError(t, res.Err())
+	err = res.Decode(sn)
+	require.NoError(t, err)
+
+	assert.Equal(t, &struct {
+		Index int64     `csv:"index"`
+		Time  time.Time `csv:"time"`
+		Name  string    `csv:"name"`
+		Score float64   `csv:"score"`
+		Sum   float64
+	}{
+		Index: 1,
+		Time:  mustParseTime("2021-02-18T22:08:44.850214724Z"),
+		Score: 5.1,
+		Name:  "John",
+		Sum:   0,
+	}, sn)
+
+	err = res.Decode(sn2)
+	require.NoError(t, err)
+	assert.Equal(t, &struct {
+		Index interface{} `csv:"index"`
+		Time  interface{} `csv:"time"`
+		Name  interface{} `csv:"name"`
+		Score interface{} `csv:"score"`
+		Sum   interface{}
+	}{
+		Index: int64(1),
+		Time:  mustParseTime("2021-02-18T22:08:44.850214724Z"),
+		Score: 5.1,
+		Name:  "John",
+		Sum:   nil,
+	}, sn2)
+
+	err = res.Decode(sn3)
+	require.NoError(t, err)
+
+	assert.Equal(t, &struct {
+		Index string `csv:"index"`
+		Time  string `csv:"time"`
+		Name  string `csv:"name"`
+		Score string `csv:"score"`
+		Sum   string
+	}{
+		Index: "1",
+		Time:  "2021-02-18T22:08:44.850214724Z",
+		Score: "5.1",
+		Name:  "John",
+		Sum:   "",
+	}, sn3)
+
+}
+
+func TestDecodeStructSkipField(t *testing.T) {
+	csvTable := `#datatype,long,double,dateTime:RFC3339Nano,string
+#default,,,,
+,Index,Score,Time,Name
+,0,3.3,2021-02-18T10:34:08.135814545Z,Thomas
+,1,5.1,2021-02-18T22:08:44.850214724Z,John
+
+`
+	reader := strings.NewReader(csvTable)
+	res := NewReader(reader)
+	require.True(t, res.NextSection())
+	require.NoError(t, res.Err())
+	require.True(t, res.NextRow())
+	require.NoError(t, res.Err())
+
+	// Test decode with skipping field
+	s := &struct {
+		Index int64
+		Time  time.Time
+		Name  string
+		Score int `csv:"-"`
+	}{}
+
+	err := res.Decode(s)
+	require.NoError(t, err)
+
+	assert.Equal(t, &struct {
+		Index int64
+		Time  time.Time
+		Name  string
+		Score int `csv:"-"`
+	}{
+		Index: 0,
+		Time:  mustParseTime("2021-02-18T10:34:08.135814545Z"),
+		Score: 0,
+		Name:  "Thomas",
+	}, s)
+}
+
+func TestDecodeStructNoTag(t *testing.T) {
+	csvTable := `#datatype,long,double,dateTime:RFC3339Nano,string
+#default,,,,
+,Index,Score,Time,Name
+,0,3.3,2021-02-18T10:34:08.135814545Z,Thomas
+,1,5.1,2021-02-18T22:08:44.850214724Z,John
+
+`
+	reader := strings.NewReader(csvTable)
+	res := NewReader(reader)
+	require.True(t, res.NextSection())
+	require.NoError(t, res.Err())
+	require.True(t, res.NextRow())
+	require.NoError(t, res.Err())
+
+	// Test decode in struct no tag
+	s := &struct {
+		Index int64
+		Time  time.Time
+		Name  string
+		Score float64
+	}{}
+
+	err := res.Decode(s)
+	require.NoError(t, err)
+
+	assert.Equal(t, &struct {
+		Index int64
+		Time  time.Time
+		Name  string
+		Score float64
+	}{
+		Index: 0,
+		Time:  mustParseTime("2021-02-18T10:34:08.135814545Z"),
+		Score: 3.3,
+		Name:  "Thomas",
+	}, s)
+}
+
+func TestDecodeStructNoMatchedFields(t *testing.T) {
+	csvTable := `#datatype,long,double,dateTime:RFC3339Nano,string
+#default,,,,
+,index,score,time,name
+,0,3.3,2021-02-18T10:34:08.135814545Z,Thomas
+,1,5.1,2021-02-18T22:08:44.850214724Z,John
+
+`
+	reader := strings.NewReader(csvTable)
+	res := NewReader(reader)
+	require.True(t, res.NextSection())
+	require.NoError(t, res.Err())
+	require.True(t, res.NextRow())
+	require.NoError(t, res.Err())
+
+	s := &struct {
+		Index int64
+		Time  time.Time
+		Name  string
+		Score float64
+	}{}
+
+	err := res.Decode(s)
+	require.NoError(t, err)
+
+	assert.Equal(t, &struct {
+		Index int64
+		Time  time.Time
+		Name  string
+		Score float64
+	}{
+		Index: 0,
+		Time:  time.Time{},
+		Score: 0.0,
+		Name:  "",
+	}, s)
+
+	// Test decode in struct no matching tag
+	sn := &struct {
+		Index int64     `csv:"Index"`
+		Time  time.Time `csv:"Time"`
+		Name  string    `csv:"Name"`
+		Score float64   `csv:"Score"`
+		Sum   float64
+	}{}
+
+	err = res.Decode(sn)
+	require.NoError(t, err)
+
+	assert.Equal(t, &struct {
+		Index int64     `csv:"Index"`
+		Time  time.Time `csv:"Time"`
+		Name  string    `csv:"Name"`
+		Score float64   `csv:"Score"`
+		Sum   float64
+	}{
+		Index: 0,
+		Time:  time.Time{},
+		Score: 0.0,
+		Name:  "",
+	}, sn)
+}
+func TestDecodeStructTagAttribute(t *testing.T) {
+	reader := strings.NewReader(createSimpleCSV("score", "long", "1"))
+	res := NewReader(reader)
+
+	require.True(t, res.NextSection())
+	require.NoError(t, res.Err())
+	require.True(t, res.NextRow())
+	require.NoError(t, res.Err())
+
+	// Test decode with tag attribute
+	s := &struct {
+		Score int `csv:"score,omitempty"`
+	}{}
+
+	err := res.Decode(s)
+	require.Error(t, err)
+	require.Equal(t, "tag attributes are not supported", err.Error())
+
+	// Test decode with empty tag attribute
+	s2 := &struct {
+		Score int `csv:"score,"`
+	}{}
+
+	err = res.Decode(s2)
+	require.NoError(t, err)
+	require.Equal(t, 1, s2.Score)
+}
+
+func TestDecodeInvalidType(t *testing.T) {
+	reader := strings.NewReader(createSimpleCSV("test", "long", "1"))
+	res := NewReader(reader)
+
+	require.True(t, res.NextSection(), res.Err())
+	require.True(t, res.NextRow(), res.Err())
+
+	err := res.Decode(map[string]int{})
+	require.Error(t, err)
+	assert.Equal(t, "cannot decode into non-pointer type map[string]int", err.Error())
+
+	s := struct {
+		Index int64     `csv:"Index"`
+		Time  time.Time `csv:"Time"`
+		Name  string    `csv:"Name"`
+		Score float64   `csv:"Score"`
+		Sum   float64
+	}{}
+
+	err = res.Decode(s)
+	require.Error(t, err)
+	assert.True(t, strings.HasPrefix(err.Error(), "cannot decode into non-pointer type struct { Index int64 \"csv:"))
+
+	sp := &s
+	sp = nil
+	err = res.Decode(sp)
+	require.Error(t, err)
+	assert.True(t, strings.HasPrefix(err.Error(), "decode into nil *struct { Index"))
+
+	s1 := struct {
+		Test fmt.Stringer `csv:"test"`
+	}{}
+	err = res.Decode(&s1)
+	require.Error(t, err)
+	assert.Equal(t, "cannot convert from column type long to fmt.Stringer", err.Error())
+
+	var r []float64
+	err = res.Decode(r)
+	require.Error(t, err)
+	assert.Equal(t, "cannot decode into non-pointer type []float64", err.Error())
+
+	err = res.Decode(&r)
+	require.Error(t, err)
+	assert.Equal(t, "cannot decode into *[]float64", err.Error())
+
+	var f float64
+	err = res.Decode(&f)
+	require.Error(t, err)
+	assert.Equal(t, "cannot decode into *float64", err.Error())
+
+	var a []fmt.Stringer
+	err = res.Decode(&a)
+	require.Error(t, err)
+	assert.Equal(t, "cannot decode into *[]fmt.Stringer", err.Error())
+
+	var p *[]string
+	err = res.Decode(p)
+	require.Error(t, err)
+	assert.Equal(t, "decode into nil *[]string", err.Error())
+
+}
+
+func TestDecodeSlice(t *testing.T) {
+	csvTable := `#datatype,string,unsignedLong,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339Nano,duration,string,long,base64Binary,boolean
+#group,false,false,true,true,false,false,true,true,true,true
+#default,_result,,,,,,,,,
+,result,table,_start,_stop,_time,took,_field,index,note,b
+,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,32m,f,-1,ZGF0YWluYmFzZTY0,true
+,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,1h23m4s,f,1,eHh4eHhjY2NjY2NkZGRkZA==,false
+
+#datatype,long,double,dateTime,string
+#default,,,,
+,index,score,time,name
+,0,3.3,2021-02-18T10:34:08.135814545Z,Thomas
+,1,5.1,2021-02-18T22:08:44.850214724Z,John
+
+`
+	reader := strings.NewReader(csvTable)
+	res := NewReader(reader)
+	require.True(t, res.NextSection())
+	require.NoError(t, res.Err())
+	require.True(t, res.NextRow())
+	require.NoError(t, res.Err())
+
+	var r []interface{}
+	require.NoError(t, res.Decode(&r))
+	er := []interface{}{
+		"_result",
+		uint64(0),
+		mustParseTime("2020-02-17T22:19:49.747562847Z"),
+		mustParseTime("2020-02-18T22:19:49.747562847Z"),
+		mustParseTime("2020-02-18T10:34:08.135814545Z"),
+		time.Minute * 32,
+		"f",
+		int64(-1),
+		[]byte("datainbase64"),
+		true,
+	}
+	require.Equal(t, er, r)
+
+	var rs []string
+	require.NoError(t, res.Decode(&rs))
+	er2 := []string{
+		"_result",
+		"0",
+		"2020-02-17T22:19:49.747562847Z",
+		"2020-02-18T22:19:49.747562847Z",
+		"2020-02-18T10:34:08.135814545Z",
+		"32m",
+		"f",
+		"-1",
+		"ZGF0YWluYmFzZTY0",
+		"true",
+	}
+	require.Equal(t, er2, rs)
+}
+
+// createSimpleCSV creates annotated CSV string with one column of given params
+func createSimpleCSV(fieldName, fieldType, value string) string {
+	return fmt.Sprintf(`#datatype,%s
+#default,
+,%s
+,%s`,
+		fieldType, fieldName, value)
+}
+
+func TestConversionErrors(t *testing.T) {
+	var singleRowDecodeTests = []struct {
+		testName    string
+		csv         string
+		value       interface{}
+		expectError string
+	}{
+		{
+			testName: "Invalid base64binary",
+			csv:      createSimpleCSV("bytes", "base64Binary", `"#"`),
+			value: struct {
+				B []byte `csv:"bytes"`
+			}{},
+			expectError: `cannot convert value "#" in column of type "base64Binary" to Go type []uint8 at line 4: illegal base64 data at input byte 0`,
+		},
+		{
+			testName: "Invalid duration",
+			csv:      createSimpleCSV("dur", "duration", `"#"`),
+			value: struct {
+				D time.Duration `csv:"dur"`
+			}{},
+			expectError: `cannot convert value "#" in column of type "duration" to Go type time.Duration at line 4: time: invalid duration "#"`,
+		},
+		{
+			testName: "Invalid time",
+			csv:      createSimpleCSV("time", "dateTime:RFC3339", `"#"`),
+			value: struct {
+				T time.Time `csv:"time"`
+			}{},
+			expectError: `cannot convert value "#" in column of type "dateTime:RFC3339" to Go type time.Time at line 4: parsing time "#" as "2006-01-02T15:04:05.999999999Z07:00": cannot parse "#" as "2006"`,
+		},
+		{
+			testName: "Invalid int",
+			csv:      createSimpleCSV("index", "long", `"#"`),
+			value: struct {
+				I int8 `csv:"index"`
+			}{},
+			expectError: `cannot convert value "#" in column of type "long" to Go type int8 at line 4: strconv.ParseInt: parsing "#": invalid syntax`,
+		},
+		{
+			testName: "Overflow int",
+			csv:      createSimpleCSV("index", "long", `1600`),
+			value: struct {
+				I int8 `csv:"index"`
+			}{},
+			expectError: `cannot convert value "1600" in column of type "long" to Go type int8 at line 4: value 1600 overflows type int8`,
+		},
+		{
+			testName: "Invalid uint",
+			csv:      createSimpleCSV("index", "unsignedLong", `"#"`),
+			value: struct {
+				U uint8 `csv:"index"`
+			}{},
+			expectError: `cannot convert value "#" in column of type "unsignedLong" to Go type uint8 at line 4: strconv.ParseUint: parsing "#": invalid syntax`,
+		},
+		{
+			testName: "Overflow uint",
+			csv:      createSimpleCSV("index", "unsignedLong", `1600`),
+			value: struct {
+				U uint8 `csv:"index"`
+			}{},
+			expectError: `cannot convert value "1600" in column of type "unsignedLong" to Go type uint8 at line 4: value 1600 overflows type uint8`,
+		},
+		{
+			testName: "Invalid bool",
+			csv:      createSimpleCSV("valid", "boolean", `"#"`),
+			value: struct {
+				B bool `csv:"valid"`
+			}{},
+			expectError: `cannot convert value "#" in column of type "boolean" to Go type bool at line 4: invalid bool value: "#"`,
+		},
+		{
+			testName: "Invalid float",
+			csv:      createSimpleCSV("mem", "double", `"#"`),
+			value: struct {
+				F float32 `csv:"mem"`
+			}{},
+			expectError: `cannot convert value "#" in column of type "double" to Go type float32 at line 4: strconv.ParseFloat: parsing "#": invalid syntax`,
+		},
+		{
+			testName: "Overflow float",
+			csv:      createSimpleCSV("mem", "double", `"1e64"`),
+			value: struct {
+				F float32 `csv:"mem"`
+			}{},
+			expectError: `cannot convert value "1e64" in column of type "double" to Go type float32 at line 4: value 1e64 overflows type float32`,
+		},
+		{
+			testName: "Invalid column type",
+			csv:      createSimpleCSV("note", "strung", `"text"`),
+			value: struct {
+				S string `csv:"note"`
+			}{"text"},
+			expectError: "",
+		},
+	}
+
+	for _, test := range singleRowDecodeTests {
+		t.Run(test.testName, func(t *testing.T) {
+			decVal := reflect.New(reflect.TypeOf(test.value))
+			r := NewReader(strings.NewReader(test.csv))
+			require.True(t, r.NextSection())
+			require.True(t, r.NextRow())
+			err := r.Decode(decVal.Interface())
+			if test.expectError != "" {
+				require.Error(t, err)
+				require.Equal(t, test.expectError, err.Error())
+				return
+			}
+			require.NoError(t, err)
+			require.Equal(t, test.value, decVal.Elem().Interface())
+		})
+	}
+
+}
+
+func TestConversionsMapping(t *testing.T) {
+	assert.NotNil(t, conversions[conv{stringCol, fieldKind(reflect.String)}])
+	assert.Nil(t, conversions[conv{stringCol, fieldKind(reflect.Bool)}])
+	assert.Nil(t, conversions[conv{stringCol, fieldKind(reflect.Int)}])
+	assert.Nil(t, conversions[conv{stringCol, fieldKind(reflect.Int8)}])
+	assert.Nil(t, conversions[conv{stringCol, fieldKind(reflect.Int16)}])
+	assert.Nil(t, conversions[conv{stringCol, fieldKind(reflect.Int32)}])
+	assert.Nil(t, conversions[conv{stringCol, fieldKind(reflect.Int64)}])
+	assert.Nil(t, conversions[conv{stringCol, fieldKind(reflect.Uint)}])
+	assert.Nil(t, conversions[conv{stringCol, fieldKind(reflect.Uint8)}])
+	assert.Nil(t, conversions[conv{stringCol, fieldKind(reflect.Uint16)}])
+	assert.Nil(t, conversions[conv{stringCol, fieldKind(reflect.Uint32)}])
+	assert.Nil(t, conversions[conv{stringCol, fieldKind(reflect.Uint64)}])
+	assert.Nil(t, conversions[conv{stringCol, fieldKind(reflect.Float32)}])
+	assert.Nil(t, conversions[conv{stringCol, fieldKind(reflect.Float64)}])
+	assert.Nil(t, conversions[conv{stringCol, durationKind}])
+	assert.Nil(t, conversions[conv{stringCol, timeKind}])
+	assert.Nil(t, conversions[conv{stringCol, bytesKind}])
+
+	assert.NotNil(t, conversions[conv{boolCol, fieldKind(reflect.String)}])
+	assert.NotNil(t, conversions[conv{boolCol, fieldKind(reflect.Bool)}])
+	assert.Nil(t, conversions[conv{boolCol, fieldKind(reflect.Int)}])
+	assert.Nil(t, conversions[conv{boolCol, fieldKind(reflect.Int8)}])
+	assert.Nil(t, conversions[conv{boolCol, fieldKind(reflect.Int16)}])
+	assert.Nil(t, conversions[conv{boolCol, fieldKind(reflect.Int32)}])
+	assert.Nil(t, conversions[conv{boolCol, fieldKind(reflect.Int64)}])
+	assert.Nil(t, conversions[conv{boolCol, fieldKind(reflect.Uint)}])
+	assert.Nil(t, conversions[conv{boolCol, fieldKind(reflect.Uint8)}])
+	assert.Nil(t, conversions[conv{boolCol, fieldKind(reflect.Uint16)}])
+	assert.Nil(t, conversions[conv{boolCol, fieldKind(reflect.Uint32)}])
+	assert.Nil(t, conversions[conv{boolCol, fieldKind(reflect.Uint64)}])
+	assert.Nil(t, conversions[conv{boolCol, fieldKind(reflect.Float32)}])
+	assert.Nil(t, conversions[conv{boolCol, fieldKind(reflect.Float64)}])
+	assert.Nil(t, conversions[conv{boolCol, durationKind}])
+	assert.Nil(t, conversions[conv{boolCol, timeKind}])
+	assert.Nil(t, conversions[conv{boolCol, bytesKind}])
+
+	assert.NotNil(t, conversions[conv{durationCol, fieldKind(reflect.String)}])
+	assert.Nil(t, conversions[conv{durationCol, fieldKind(reflect.Bool)}])
+	assert.Nil(t, conversions[conv{durationCol, fieldKind(reflect.Int)}])
+	assert.Nil(t, conversions[conv{durationCol, fieldKind(reflect.Int8)}])
+	assert.Nil(t, conversions[conv{durationCol, fieldKind(reflect.Int16)}])
+	assert.Nil(t, conversions[conv{durationCol, fieldKind(reflect.Int32)}])
+	assert.Nil(t, conversions[conv{durationCol, fieldKind(reflect.Int64)}])
+	assert.Nil(t, conversions[conv{durationCol, fieldKind(reflect.Uint)}])
+	assert.Nil(t, conversions[conv{durationCol, fieldKind(reflect.Uint8)}])
+	assert.Nil(t, conversions[conv{durationCol, fieldKind(reflect.Uint16)}])
+	assert.Nil(t, conversions[conv{durationCol, fieldKind(reflect.Uint32)}])
+	assert.Nil(t, conversions[conv{durationCol, fieldKind(reflect.Uint64)}])
+	assert.Nil(t, conversions[conv{durationCol, fieldKind(reflect.Float32)}])
+	assert.Nil(t, conversions[conv{durationCol, fieldKind(reflect.Float64)}])
+	assert.NotNil(t, conversions[conv{durationCol, durationKind}])
+	assert.Nil(t, conversions[conv{durationCol, timeKind}])
+	assert.Nil(t, conversions[conv{durationCol, bytesKind}])
+
+	assert.NotNil(t, conversions[conv{longCol, fieldKind(reflect.String)}])
+	assert.Nil(t, conversions[conv{longCol, fieldKind(reflect.Bool)}])
+	assert.NotNil(t, conversions[conv{longCol, fieldKind(reflect.Int)}])
+	assert.NotNil(t, conversions[conv{longCol, fieldKind(reflect.Int8)}])
+	assert.NotNil(t, conversions[conv{longCol, fieldKind(reflect.Int16)}])
+	assert.NotNil(t, conversions[conv{longCol, fieldKind(reflect.Int32)}])
+	assert.NotNil(t, conversions[conv{longCol, fieldKind(reflect.Int64)}])
+	assert.Nil(t, conversions[conv{longCol, fieldKind(reflect.Uint)}])
+	assert.Nil(t, conversions[conv{longCol, fieldKind(reflect.Uint8)}])
+	assert.Nil(t, conversions[conv{longCol, fieldKind(reflect.Uint16)}])
+	assert.Nil(t, conversions[conv{longCol, fieldKind(reflect.Uint32)}])
+	assert.Nil(t, conversions[conv{longCol, fieldKind(reflect.Uint64)}])
+	assert.NotNil(t, conversions[conv{longCol, fieldKind(reflect.Float32)}])
+	assert.NotNil(t, conversions[conv{longCol, fieldKind(reflect.Float64)}])
+	assert.Nil(t, conversions[conv{longCol, durationKind}])
+	assert.Nil(t, conversions[conv{longCol, timeKind}])
+	assert.Nil(t, conversions[conv{longCol, bytesKind}])
+
+	assert.NotNil(t, conversions[conv{unsignedLongCol, fieldKind(reflect.String)}])
+	assert.Nil(t, conversions[conv{unsignedLongCol, fieldKind(reflect.Bool)}])
+	assert.NotNil(t, conversions[conv{unsignedLongCol, fieldKind(reflect.Int)}])
+	assert.NotNil(t, conversions[conv{unsignedLongCol, fieldKind(reflect.Int8)}])
+	assert.NotNil(t, conversions[conv{unsignedLongCol, fieldKind(reflect.Int16)}])
+	assert.NotNil(t, conversions[conv{unsignedLongCol, fieldKind(reflect.Int32)}])
+	assert.NotNil(t, conversions[conv{unsignedLongCol, fieldKind(reflect.Int64)}])
+	assert.NotNil(t, conversions[conv{unsignedLongCol, fieldKind(reflect.Uint)}])
+	assert.NotNil(t, conversions[conv{unsignedLongCol, fieldKind(reflect.Uint8)}])
+	assert.NotNil(t, conversions[conv{unsignedLongCol, fieldKind(reflect.Uint16)}])
+	assert.NotNil(t, conversions[conv{unsignedLongCol, fieldKind(reflect.Uint32)}])
+	assert.NotNil(t, conversions[conv{unsignedLongCol, fieldKind(reflect.Uint64)}])
+	assert.NotNil(t, conversions[conv{unsignedLongCol, fieldKind(reflect.Float32)}])
+	assert.NotNil(t, conversions[conv{unsignedLongCol, fieldKind(reflect.Float64)}])
+	assert.Nil(t, conversions[conv{unsignedLongCol, durationKind}])
+	assert.Nil(t, conversions[conv{unsignedLongCol, timeKind}])
+	assert.Nil(t, conversions[conv{unsignedLongCol, bytesKind}])
+
+	assert.NotNil(t, conversions[conv{doubleCol, fieldKind(reflect.String)}])
+	assert.Nil(t, conversions[conv{doubleCol, fieldKind(reflect.Bool)}])
+	assert.Nil(t, conversions[conv{doubleCol, fieldKind(reflect.Int)}])
+	assert.Nil(t, conversions[conv{doubleCol, fieldKind(reflect.Int8)}])
+	assert.Nil(t, conversions[conv{doubleCol, fieldKind(reflect.Int16)}])
+	assert.Nil(t, conversions[conv{doubleCol, fieldKind(reflect.Int32)}])
+	assert.Nil(t, conversions[conv{doubleCol, fieldKind(reflect.Int64)}])
+	assert.Nil(t, conversions[conv{doubleCol, fieldKind(reflect.Uint)}])
+	assert.Nil(t, conversions[conv{doubleCol, fieldKind(reflect.Uint8)}])
+	assert.Nil(t, conversions[conv{doubleCol, fieldKind(reflect.Uint16)}])
+	assert.Nil(t, conversions[conv{doubleCol, fieldKind(reflect.Uint32)}])
+	assert.Nil(t, conversions[conv{doubleCol, fieldKind(reflect.Uint64)}])
+	assert.NotNil(t, conversions[conv{doubleCol, fieldKind(reflect.Float32)}])
+	assert.NotNil(t, conversions[conv{doubleCol, fieldKind(reflect.Float64)}])
+	assert.Nil(t, conversions[conv{doubleCol, durationKind}])
+	assert.Nil(t, conversions[conv{doubleCol, timeKind}])
+	assert.Nil(t, conversions[conv{doubleCol, bytesKind}])
+
+	assert.NotNil(t, conversions[conv{base64BinaryCol, fieldKind(reflect.String)}])
+	assert.Nil(t, conversions[conv{base64BinaryCol, fieldKind(reflect.Bool)}])
+	assert.Nil(t, conversions[conv{base64BinaryCol, fieldKind(reflect.Int)}])
+	assert.Nil(t, conversions[conv{base64BinaryCol, fieldKind(reflect.Int8)}])
+	assert.Nil(t, conversions[conv{base64BinaryCol, fieldKind(reflect.Int16)}])
+	assert.Nil(t, conversions[conv{base64BinaryCol, fieldKind(reflect.Int32)}])
+	assert.Nil(t, conversions[conv{base64BinaryCol, fieldKind(reflect.Int64)}])
+	assert.Nil(t, conversions[conv{base64BinaryCol, fieldKind(reflect.Uint)}])
+	assert.Nil(t, conversions[conv{base64BinaryCol, fieldKind(reflect.Uint8)}])
+	assert.Nil(t, conversions[conv{base64BinaryCol, fieldKind(reflect.Uint16)}])
+	assert.Nil(t, conversions[conv{base64BinaryCol, fieldKind(reflect.Uint32)}])
+	assert.Nil(t, conversions[conv{base64BinaryCol, fieldKind(reflect.Uint64)}])
+	assert.Nil(t, conversions[conv{base64BinaryCol, fieldKind(reflect.Float32)}])
+	assert.Nil(t, conversions[conv{base64BinaryCol, fieldKind(reflect.Float64)}])
+	assert.Nil(t, conversions[conv{base64BinaryCol, durationKind}])
+	assert.Nil(t, conversions[conv{base64BinaryCol, timeKind}])
+	assert.NotNil(t, conversions[conv{base64BinaryCol, bytesKind}])
+
+	assert.NotNil(t, conversions[conv{rfc3339Col, fieldKind(reflect.String)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Bool)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Int)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Int8)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Int16)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Int32)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Int64)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Uint)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Uint8)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Uint16)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Uint32)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Uint64)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Float32)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Float64)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, durationKind}])
+	assert.NotNil(t, conversions[conv{rfc3339Col, timeKind}])
+	assert.Nil(t, conversions[conv{rfc3339Col, bytesKind}])
+
+	assert.NotNil(t, conversions[conv{rfc3339Col, fieldKind(reflect.String)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Bool)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Int)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Int8)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Int16)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Int32)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Int64)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Uint)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Uint8)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Uint16)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Uint32)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Uint64)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Float32)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, fieldKind(reflect.Float64)}])
+	assert.Nil(t, conversions[conv{rfc3339Col, durationKind}])
+	assert.NotNil(t, conversions[conv{rfc3339Col, timeKind}])
+	assert.Nil(t, conversions[conv{rfc3339Col, bytesKind}])
+}
+
+// mustParseTime parses s as an RFC3339 timestamp and panics on error.
+func mustParseTime(s string) time.Time {
+	t, err := time.Parse(time.RFC3339, s)
+	if err != nil {
+		panic(err)
+	}
+	return t
+}
diff --git a/annotatedcsv/reader_test.go b/annotatedcsv/reader_test.go
index d38c568..b949e75 100644
--- a/annotatedcsv/reader_test.go
+++ b/annotatedcsv/reader_test.go
@@ -27,15 +27,15 @@ func TestSingleTable(t *testing.T) {
 	tables := []expectedTable{
 		{columns: []annotatedcsv.Column{
 			{Type: "string", Default: "_result", Name: "result", Group: false},
-			{Type: "long", Default: nil, Name: "table", Group: false},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_start", Group: true},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_stop", Group: true},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_time", Group: false},
-			{Type: "double", Default: nil, Name: "_value", Group: false},
-			{Type: "string", Default: nil, Name: "_field", Group: true},
-			{Type: "string", Default: nil, Name: "_measurement", Group: true},
-			{Type: "string", Default: nil, Name: "a", Group: true},
-			{Type: "string", Default: nil, Name: "b", Group: true},
+			{Type: "long", Default: "", Name: "table", Group: false},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_start", Group: true},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_stop", Group: true},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_time", Group: false},
+			{Type: "double", Default: "", Name: "_value", Group: false},
+			{Type: "string", Default: "", Name: "_field", Group: true},
+			{Type: "string", Default: "", Name: "_measurement", Group: true},
+			{Type: "string", Default: "", Name: "a", Group: true},
+			{Type: "string", Default: "", Name: "b", Group: true},
 		},
 			rows: [][]interface{}{
 				{
@@ -103,15 +103,15 @@ func TestMultiTables(t *testing.T) {
 		{ // Table 1
 			columns: []annotatedcsv.Column{
 				{Type: "string", Default: "_result", Name: "result", Group: false},
-				{Type: "long", Default: nil, Name: "table", Group: false},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_start", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_stop", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_time", Group: false},
-				{Type: "double", Default: nil, Name: "_value", Group: false},
-				{Type: "string", Default: nil, Name: "_field", Group: true},
-				{Type: "string", Default: nil, Name: "_measurement", Group: true},
-				{Type: "string", Default: nil, Name: "a", Group: true},
-				{Type: "string", Default: nil, Name: "b", Group: true},
+				{Type: "long", Default: "", Name: "table", Group: false},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_start", Group: true},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_stop", Group: true},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_time", Group: false},
+				{Type: "double", Default: "", Name: "_value", Group: false},
+				{Type: "string", Default: "", Name: "_field", Group: true},
+				{Type: "string", Default: "", Name: "_measurement", Group: true},
+				{Type: "string", Default: "", Name: "a", Group: true},
+				{Type: "string", Default: "", Name: "b", Group: true},
 			},
 			rows: [][]interface{}{
 				{"_result",
@@ -142,15 +142,15 @@ func TestMultiTables(t *testing.T) {
 		{ //Table 2
 			columns: []annotatedcsv.Column{
 				{Type: "string", Default: "_result", Name: "result", Group: false},
-				{Type: "long", Default: nil, Name: "table", Group: false},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_start", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_stop", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_time", Group: false},
-				{Type: "long", Default: nil, Name: "_value", Group: false},
-				{Type: "string", Default: nil, Name: "_field", Group: true},
-				{Type: "string", Default: nil, Name: "_measurement", Group: true},
-				{Type: "string", Default: nil, Name: "a", Group: true},
-				{Type: "string", Default: nil, Name: "b", Group: true},
+				{Type: "long", Default: "", Name: "table", Group: false},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_start", Group: true},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_stop", Group: true},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_time", Group: false},
+				{Type: "long", Default: "", Name: "_value", Group: false},
+				{Type: "string", Default: "", Name: "_field", Group: true},
+				{Type: "string", Default: "", Name: "_measurement", Group: true},
+				{Type: "string", Default: "", Name: "a", Group: true},
+				{Type: "string", Default: "", Name: "b", Group: true},
 			},
 			rows: [][]interface{}{
 				{"_result",
@@ -181,15 +181,15 @@ func TestMultiTables(t *testing.T) {
 		{ // Table 3
 			columns: []annotatedcsv.Column{
 				{Type: "string", Default: "_result", Name: "result", Group: false},
-				{Type: "long", Default: nil, Name: "table", Group: false},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_start", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_stop", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_time", Group: false},
-				{Type: "boolean", Default: nil, Name: "_value", Group: false},
-				{Type: "string", Default: nil, Name: "_field", Group: true},
-				{Type: "string", Default: nil, Name: "_measurement", Group: true},
-				{Type: "string", Default: nil, Name: "a", Group: true},
-				{Type: "string", Default: nil, Name: "b", Group: true},
+				{Type: "long", Default: "", Name: "table", Group: false},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_start", Group: true},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_stop", Group: true},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_time", Group: false},
+				{Type: "boolean", Default: "", Name: "_value", Group: false},
+				{Type: "string", Default: "", Name: "_field", Group: true},
+				{Type: "string", Default: "", Name: "_measurement", Group: true},
+				{Type: "string", Default: "", Name: "a", Group: true},
+				{Type: "string", Default: "", Name: "b", Group: true},
 			},
 			rows: [][]interface{}{
 				{"_result",
@@ -220,15 +220,15 @@ func TestMultiTables(t *testing.T) {
 		{ //Table 4
 			columns: []annotatedcsv.Column{
 				{Type: "string", Default: "_result", Name: "result", Group: false},
-				{Type: "long", Default: nil, Name: "table", Group: false},
-				{Type: "dateTime:RFC3339Nano", Default: nil, Name: "_start", Group: true},
-				{Type: "dateTime:RFC3339Nano", Default: nil, Name: "_stop", Group: true},
-				{Type: "dateTime:RFC3339Nano", Default: nil, Name: "_time", Group: false},
-				{Type: "unsignedLong", Default: nil, Name: "_value", Group: false},
-				{Type: "string", Default: nil, Name: "_field", Group: true},
-				{Type: "string", Default: nil, Name: "_measurement", Group: true},
-				{Type: "string", Default: nil, Name: "a", Group: true},
-				{Type: "string", Default: nil, Name: "b", Group: true},
+				{Type: "long", Default: "", Name: "table", Group: false},
+				{Type: "dateTime:RFC3339Nano", Default: "", Name: "_start", Group: true},
+				{Type: "dateTime:RFC3339Nano", Default: "", Name: "_stop", Group: true},
+				{Type: "dateTime:RFC3339Nano", Default: "", Name: "_time", Group: false},
+				{Type: "unsignedLong", Default: "", Name: "_value", Group: false},
+				{Type: "string", Default: "", Name: "_field", Group: true},
+				{Type: "string", Default: "", Name: "_measurement", Group: true},
+				{Type: "string", Default: "", Name: "a", Group: true},
+				{Type: "string", Default: "", Name: "b", Group: true},
 			},
 			rows: [][]interface{}{
 				{"_result",
@@ -264,28 +264,31 @@ func TestMultiTables(t *testing.T) {
 	res := annotatedcsv.NewReader(reader)
 
 	require.False(t, res.NextRow())
-	require.Nil(t, res.Err())
+	require.NoError(t, res.Err())
 	require.True(t, res.NextSection())
-	require.Nil(t, res.Err())
+	require.NoError(t, res.Err())
 	require.True(t, res.NextRow())
-	require.Nil(t, res.Err())
-	require.Equal(t, tablesMultiStructure[0].rows[0], res.Row())
+	require.NoError(t, res.Err())
+	var row []interface{}
+	require.NoError(t, res.Decode(&row))
+	require.Equal(t, tablesMultiStructure[0].rows[0], row)
 
 	reader = strings.NewReader(csvTableMultiStructure)
 	res = annotatedcsv.NewReader(reader)
 
 	//test skip tables
 	require.True(t, res.NextSection())
-	require.Nil(t, res.Err())
+	require.NoError(t, res.Err())
 	require.True(t, res.NextSection())
-	require.Nil(t, res.Err())
+	require.NoError(t, res.Err())
 	require.True(t, res.NextRow())
-	require.Nil(t, res.Err())
+	require.NoError(t, res.Err())
 	require.True(t, res.NextSection())
-	require.Nil(t, res.Err())
+	require.NoError(t, res.Err())
 	require.True(t, res.NextRow())
-	require.Nil(t, res.Err())
-	require.Equal(t, tablesMultiStructure[2].rows[0], res.Row())
+	require.NoError(t, res.Err())
+	require.NoError(t, res.Decode(&row))
+	require.Equal(t, tablesMultiStructure[2].rows[0], row)
 
 	csvTableMultiTables := `#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string
 #group,false,false,true,true,false,false,true,true,true,true
@@ -305,15 +308,15 @@ func TestMultiTables(t *testing.T) {
 		{
 			columns: []annotatedcsv.Column{
 				{Type: "string", Default: "_result", Name: "result", Group: false},
-				{Type: "long", Default: nil, Name: "table", Group: false},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_start", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_stop", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_time", Group: false},
-				{Type: "double", Default: nil, Name: "_value", Group: false},
-				{Type: "string", Default: nil, Name: "_field", Group: true},
-				{Type: "string", Default: nil, Name: "_measurement", Group: true},
-				{Type: "string", Default: nil, Name: "a", Group: true},
-				{Type: "string", Default: nil, Name: "b", Group: true},
+				{Type: "long", Default: "", Name: "table", Group: false},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_start", Group: true},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_stop", Group: true},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_time", Group: false},
+				{Type: "double", Default: "", Name: "_value", Group: false},
+				{Type: "string", Default: "", Name: "_field", Group: true},
+				{Type: "string", Default: "", Name: "_measurement", Group: true},
+				{Type: "string", Default: "", Name: "a", Group: true},
+				{Type: "string", Default: "", Name: "b", Group: true},
 			},
 			rows: [][]interface{}{
 				{"_result",
@@ -417,126 +420,9 @@ func TestMultiTables(t *testing.T) {
 	reader = strings.NewReader(csvTableMultiTables)
 	res = annotatedcsv.NewReader(reader)
 	require.True(t, res.NextSection())
-	require.Nil(t, res.Err())
+	require.NoError(t, res.Err())
 	require.False(t, res.NextSection())
-	require.Nil(t, res.Err())
-}
-
-func TestValueByName(t *testing.T) {
-	csvTable := `#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,long,string,duration,base64Binary,dateTime:RFC3339
-#group,false,false,true,true,false,true,true,false,false,false
-#default,_result,,,,,,,,,
-,result,table,_start,_stop,_time,deviceId,sensor,elapsed,note,start
-,,0,2020-04-28T12:36:50.990018157Z,2020-04-28T12:51:50.990018157Z,2020-04-28T12:38:11.480545389Z,1467463,BME280,1m1s,ZGF0YWluYmFzZTY0,2020-04-27T00:00:00Z
-,,1,2020-04-28T12:36:50.990018157Z,2020-04-28T12:51:50.990018157Z,2020-04-28T12:39:36.330153686Z,1467463,BME280,1h20m30.13245s,eHh4eHhjY2NjY2NkZGRkZA==,2020-04-28T00:00:00Z
-
-#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string
-#group,false,false,true,true,false,false,true,true,true,true
-#default,_result,,,,,,,,,
-,result,table,_start,_stop,_time,_value,_field,_measurement,a,b
-,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,1.4,f,test,1,adsfasdf
-,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,6.6,f,test,1,adsfasdf
-
-`
-	tables := []expectedTable{
-		{ // Table 1
-			columns: []annotatedcsv.Column{
-				{Type: "string", Default: "_result", Name: "result", Group: false},
-				{Type: "long", Default: nil, Name: "table", Group: false},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_start", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_stop", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_time", Group: false},
-				{Type: "long", Default: nil, Name: "deviceId", Group: true},
-				{Type: "string", Default: nil, Name: "sensor", Group: true},
-				{Type: "duration", Default: nil, Name: "elapsed", Group: false},
-				{Type: "base64Binary", Default: nil, Name: "note", Group: false},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "start", Group: false},
-			},
-			rows: [][]interface{}{
-				{
-					"_result",
-					int64(0),
-					mustParseTime("2020-04-28T12:36:50.990018157Z"),
-					mustParseTime("2020-04-28T12:51:50.990018157Z"),
-					mustParseTime("2020-04-28T12:38:11.480545389Z"),
-					int64(1467463),
-					"BME280",
-					time.Minute + time.Second,
-					[]byte("datainbase64"),
-					time.Date(2020, 4, 27, 0, 0, 0, 0, time.UTC),
-				},
-				{
-					"_result",
-					int64(1),
-					mustParseTime("2020-04-28T12:36:50.990018157Z"),
-					mustParseTime("2020-04-28T12:51:50.990018157Z"),
-					mustParseTime("2020-04-28T12:39:36.330153686Z"),
-					int64(1467463),
-					"BME280",
-					time.Hour + 20*time.Minute + 30*time.Second + 132450000*time.Nanosecond,
-					[]byte("xxxxxccccccddddd"),
-					time.Date(2020, 4, 28, 0, 0, 0, 0, time.UTC),
-				},
-			},
-		},
-		{
-			columns: []annotatedcsv.Column{
-				{Type: "string", Default: "_result", Name: "result", Group: false},
-				{Type: "long", Default: nil, Name: "table", Group: false},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_start", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_stop", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_time", Group: false},
-				{Type: "double", Default: nil, Name: "_value", Group: false},
-				{Type: "string", Default: nil, Name: "_field", Group: true},
-				{Type: "string", Default: nil, Name: "_measurement", Group: true},
-				{Type: "string", Default: nil, Name: "a", Group: true},
-				{Type: "string", Default: nil, Name: "b", Group: true},
-			},
-			rows: [][]interface{}{
-				{
-					"_result",
-					int64(0),
-					mustParseTime("2020-02-17T22:19:49.747562847Z"),
-					mustParseTime("2020-02-18T22:19:49.747562847Z"),
-					mustParseTime("2020-02-18T10:34:08.135814545Z"),
-					1.4,
-					"f",
-					"test",
-					"1",
-					"adsfasdf",
-				},
-				{
-					"_result",
-					int64(0),
-					mustParseTime("2020-02-17T22:19:49.747562847Z"),
-					mustParseTime("2020-02-18T22:19:49.747562847Z"),
-					mustParseTime("2020-02-18T22:08:44.850214724Z"),
-					6.6,
-					"f",
-					"test",
-					"1",
-					"adsfasdf",
-				},
-			},
-		},
-	}
-
-	verifyTables(t, csvTable, tables)
-
-	reader := strings.NewReader(csvTable)
-	res := annotatedcsv.NewReader(reader)
-
-	require.True(t, res.NextSection() && res.NextRow(), res.Err())
-	require.Nil(t, res.Err())
-	assert.Equal(t, []byte("datainbase64"), res.ValueByName("note"))
-	assert.Nil(t, res.ValueByName(""))
-	assert.Nil(t, res.ValueByName("invalid"))
-	assert.Nil(t, res.ValueByName("a"))
-
-	require.True(t, res.NextSection() && res.NextRow(), res.Err())
-	assert.Equal(t, "1", res.ValueByName("a"))
-	assert.Nil(t, res.ValueByName("note"))
-
+	require.NoError(t, res.Err())
 }
 
 func TestErrorInRow(t *testing.T) {
@@ -551,8 +437,8 @@ func TestErrorInRow(t *testing.T) {
 	tables := []expectedTable{
 		{ // Table 1
 			[]annotatedcsv.Column{
-				{Type: "string", Default: nil, Name: "error", Group: true},
-				{Type: "string", Default: nil, Name: "reference", Group: true},
+				{Type: "string", Default: "", Name: "error", Group: true},
+				{Type: "string", Default: "", Name: "reference", Group: true},
 			},
 			[][]interface{}{
 				{
@@ -574,8 +460,8 @@ func TestErrorInRow(t *testing.T) {
 	tables = []expectedTable{
 		{ // Table 1
 			[]annotatedcsv.Column{
-				{Type: "string", Default: nil, Name: "error", Group: true},
-				{Type: "string", Default: nil, Name: "reference", Group: true},
+				{Type: "string", Default: "", Name: "error", Group: true},
+				{Type: "string", Default: "", Name: "reference", Group: true},
 			},
 			[][]interface{}{
 				{
@@ -597,8 +483,8 @@ func TestErrorInRow(t *testing.T) {
 	tables = []expectedTable{
 		{ // Table 1
 			[]annotatedcsv.Column{
-				{Type: "string", Default: nil, Name: "error", Group: true},
-				{Type: "string", Default: nil, Name: "reference", Group: true},
+				{Type: "string", Default: "", Name: "error", Group: true},
+				{Type: "string", Default: "", Name: "reference", Group: true},
 			},
 			[][]interface{}{
 				{
@@ -623,15 +509,15 @@ func TestInvalidDataType(t *testing.T) {
 	tables := []expectedTable{
 		{[]annotatedcsv.Column{
 			{Type: "string", Default: "_result", Name: "result", Group: false},
-			{Type: "long", Default: nil, Name: "table", Group: false},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_start", Group: true},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_stop", Group: true},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_time", Group: false},
-			{Type: "int", Default: nil, Name: "_value", Group: false},
-			{Type: "string", Default: nil, Name: "_field", Group: true},
-			{Type: "string", Default: nil, Name: "_measurement", Group: true},
-			{Type: "string", Default: nil, Name: "a", Group: true},
-			{Type: "string", Default: nil, Name: "b", Group: true},
+			{Type: "long", Default: "", Name: "table", Group: false},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_start", Group: true},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_stop", Group: true},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_time", Group: false},
+			{Type: "int", Default: "", Name: "_value", Group: false},
+			{Type: "string", Default: "", Name: "_field", Group: true},
+			{Type: "string", Default: "", Name: "_measurement", Group: true},
+			{Type: "string", Default: "", Name: "a", Group: true},
+			{Type: "string", Default: "", Name: "b", Group: true},
 		},
 			[][]interface{}{
 				{
@@ -677,15 +563,15 @@ func TestReorderedAnnotations(t *testing.T) {
 	tables := []expectedTable{
 		{[]annotatedcsv.Column{
 			{Type: "string", Default: "_result", Name: "result", Group: false},
-			{Type: "long", Default: nil, Name: "table", Group: false},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_start", Group: true},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_stop", Group: true},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_time", Group: false},
-			{Type: "double", Default: nil, Name: "_value", Group: false},
-			{Type: "string", Default: nil, Name: "_field", Group: true},
-			{Type: "string", Default: nil, Name: "_measurement", Group: true},
-			{Type: "string", Default: nil, Name: "a", Group: true},
-			{Type: "string", Default: nil, Name: "b", Group: true},
+			{Type: "long", Default: "", Name: "table", Group: false},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_start", Group: true},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_stop", Group: true},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_time", Group: false},
+			{Type: "double", Default: "", Name: "_value", Group: false},
+			{Type: "string", Default: "", Name: "_field", Group: true},
+			{Type: "string", Default: "", Name: "_measurement", Group: true},
+			{Type: "string", Default: "", Name: "a", Group: true},
+			{Type: "string", Default: "", Name: "b", Group: true},
 		},
 			[][]interface{}{
 				{
@@ -738,16 +624,16 @@ func TestDatatypeOnlyAnnotation(t *testing.T) {
 `
 	tables := []expectedTable{
 		{[]annotatedcsv.Column{
-			{Type: "string", Default: nil, Name: "result", Group: false},
-			{Type: "long", Default: nil, Name: "table", Group: false},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_start", Group: false},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_stop", Group: false},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_time", Group: false},
-			{Type: "double", Default: nil, Name: "_value", Group: false},
-			{Type: "string", Default: nil, Name: "_field", Group: false},
-			{Type: "string", Default: nil, Name: "_measurement", Group: false},
-			{Type: "string", Default: nil, Name: "a", Group: false},
-			{Type: "string", Default: nil, Name: "b", Group: false},
+			{Type: "string", Default: "", Name: "result", Group: false},
+			{Type: "long", Default: "", Name: "table", Group: false},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_start", Group: false},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_stop", Group: false},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_time", Group: false},
+			{Type: "double", Default: "", Name: "_value", Group: false},
+			{Type: "string", Default: "", Name: "_field", Group: false},
+			{Type: "string", Default: "", Name: "_measurement", Group: false},
+			{Type: "string", Default: "", Name: "a", Group: false},
+			{Type: "string", Default: "", Name: "b", Group: false},
 		},
 			[][]interface{}{
 				{
@@ -793,15 +679,15 @@ func TestMissingDatatypeAnnotation(t *testing.T) {
 		{ // Table 1
 			columns: []annotatedcsv.Column{
 				{Type: "", Default: "_result", Name: "result", Group: false},
-				{Type: "", Default: nil, Name: "table", Group: false},
-				{Type: "", Default: nil, Name: "_start", Group: true},
-				{Type: "", Default: nil, Name: "_stop", Group: true},
-				{Type: "", Default: nil, Name: "_time", Group: false},
-				{Type: "", Default: nil, Name: "deviceId", Group: true},
-				{Type: "", Default: nil, Name: "sensor", Group: true},
-				{Type: "", Default: nil, Name: "elapsed", Group: false},
-				{Type: "", Default: nil, Name: "note", Group: false},
-				{Type: "", Default: nil, Name: "start", Group: false},
+				{Type: "", Default: "", Name: "table", Group: false},
+				{Type: "", Default: "", Name: "_start", Group: true},
+				{Type: "", Default: "", Name: "_stop", Group: true},
+				{Type: "", Default: "", Name: "_time", Group: false},
+				{Type: "", Default: "", Name: "deviceId", Group: true},
+				{Type: "", Default: "", Name: "sensor", Group: true},
+				{Type: "", Default: "", Name: "elapsed", Group: false},
+				{Type: "", Default: "", Name: "note", Group: false},
+				{Type: "", Default: "", Name: "start", Group: false},
 			},
 			rows: [][]interface{}{
 				{
@@ -853,16 +739,16 @@ func TestMissingAnnotations(t *testing.T) {
 	tables := []expectedTable{
 		{ // Table 1
 			columns: []annotatedcsv.Column{
-				{Type: "", Default: nil, Name: "result", Group: false},
-				{Type: "", Default: nil, Name: "table", Group: false},
-				{Type: "", Default: nil, Name: "_start", Group: false},
-				{Type: "", Default: nil, Name: "_stop", Group: false},
-				{Type: "", Default: nil, Name: "_time", Group: false},
-				{Type: "", Default: nil, Name: "deviceId", Group: false},
-				{Type: "", Default: nil, Name: "sensor", Group: false},
-				{Type: "", Default: nil, Name: "elapsed", Group: false},
-				{Type: "", Default: nil, Name: "note", Group: false},
-				{Type: "", Default: nil, Name: "start", Group: false},
+				{Type: "", Default: "", Name: "result", Group: false},
+				{Type: "", Default: "", Name: "table", Group: false},
+				{Type: "", Default: "", Name: "_start", Group: false},
+				{Type: "", Default: "", Name: "_stop", Group: false},
+				{Type: "", Default: "", Name: "_time", Group: false},
+				{Type: "", Default: "", Name: "deviceId", Group: false},
+				{Type: "", Default: "", Name: "sensor", Group: false},
+				{Type: "", Default: "", Name: "elapsed", Group: false},
+				{Type: "", Default: "", Name: "note", Group: false},
+				{Type: "", Default: "", Name: "start", Group: false},
 			},
 			rows: [][]interface{}{
 				{
@@ -908,15 +794,15 @@ func TestUnexpectedAnnotation(t *testing.T) {
 	tables := []expectedTable{
 		{[]annotatedcsv.Column{
 			{Type: "string", Default: "_result", Name: "result", Group: false},
-			{Type: "long", Default: nil, Name: "table", Group: false},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_start", Group: true},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_stop", Group: true},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_time", Group: false},
-			{Type: "double", Default: nil, Name: "_value", Group: false},
-			{Type: "string", Default: nil, Name: "_field", Group: true},
-			{Type: "string", Default: nil, Name: "_measurement", Group: true},
-			{Type: "string", Default: nil, Name: "a", Group: true},
-			{Type: "string", Default: nil, Name: "b", Group: true},
+			{Type: "long", Default: "", Name: "table", Group: false},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_start", Group: true},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_stop", Group: true},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_time", Group: false},
+			{Type: "double", Default: "", Name: "_value", Group: false},
+			{Type: "string", Default: "", Name: "_field", Group: true},
+			{Type: "string", Default: "", Name: "_measurement", Group: true},
+			{Type: "string", Default: "", Name: "a", Group: true},
+			{Type: "string", Default: "", Name: "b", Group: true},
 		},
 			[][]interface{}{
 				{
@@ -962,15 +848,15 @@ func TestDatetimeConversion(t *testing.T) {
 	tables := []expectedTable{
 		{[]annotatedcsv.Column{
 			{Type: "string", Default: "_result", Name: "result", Group: false},
-			{Type: "long", Default: nil, Name: "table", Group: false},
-			{Type: "dateTime:RFC3339", Default: nil, Name: "_start", Group: true},
-			{Type: "dateTime", Default: nil, Name: "_stop", Group: true},
-			{Type: "dateTime:RFC3339Nano", Default: nil, Name: "_time", Group: false},
-			{Type: "double", Default: nil, Name: "_value", Group: false},
-			{Type: "string", Default: nil, Name: "_field", Group: true},
-			{Type: "string", Default: nil, Name: "_measurement", Group: true},
-			{Type: "string", Default: nil, Name: "a", Group: true},
-			{Type: "string", Default: nil, Name: "b", Group: true},
+			{Type: "long", Default: "", Name: "table", Group: false},
+			{Type: "dateTime:RFC3339", Default: "", Name: "_start", Group: true},
+			{Type: "dateTime", Default: "", Name: "_stop", Group: true},
+			{Type: "dateTime:RFC3339Nano", Default: "", Name: "_time", Group: false},
+			{Type: "double", Default: "", Name: "_value", Group: false},
+			{Type: "string", Default: "", Name: "_field", Group: true},
+			{Type: "string", Default: "", Name: "_measurement", Group: true},
+			{Type: "string", Default: "", Name: "a", Group: true},
+			{Type: "string", Default: "", Name: "b", Group: true},
 		},
 			[][]interface{}{
 				{
@@ -1015,15 +901,15 @@ func TestDatetimeConversion(t *testing.T) {
 	tables = []expectedTable{
 		{[]annotatedcsv.Column{
 			{Type: "string", Default: "_result", Name: "result", Group: false},
-			{Type: "long", Default: nil, Name: "table", Group: false},
-			{Type: "dateTime:wrongLayout", Default: nil, Name: "_start", Group: true},
-			{Type: "dateTime", Default: nil, Name: "_stop", Group: true},
-			{Type: "dateTime:RFC3339Nano", Default: nil, Name: "_time", Group: false},
-			{Type: "double", Default: nil, Name: "_value", Group: false},
-			{Type: "string", Default: nil, Name: "_field", Group: true},
-			{Type: "string", Default: nil, Name: "_measurement", Group: true},
-			{Type: "string", Default: nil, Name: "a", Group: true},
-			{Type: "string", Default: nil, Name: "b", Group: true},
+			{Type: "long", Default: "", Name: "table", Group: false},
+			{Type: "dateTime:wrongLayout", Default: "", Name: "_start", Group: true},
+			{Type: "dateTime", Default: "", Name: "_stop", Group: true},
+			{Type: "dateTime:RFC3339Nano", Default: "", Name: "_time", Group: false},
+			{Type: "double", Default: "", Name: "_value", Group: false},
+			{Type: "string", Default: "", Name: "_field", Group: true},
+			{Type: "string", Default: "", Name: "_measurement", Group: true},
+			{Type: "string", Default: "", Name: "a", Group: true},
+			{Type: "string", Default: "", Name: "b", Group: true},
 		},
 			[][]interface{}{
 				{
@@ -1063,11 +949,11 @@ func TestFailedConversion(t *testing.T) {
 #group,false,false,true,true,false,false,true,true,true,true
 #default,_result,zero,,,,,,,,
 ,result,table,_start,_stop,_time,_value,_field,_measurement,a,b
-,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,seven,f,test,1,adsfasdf
+,,,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,seven,f,test,1,adsfasdf
 ,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,six,f,test,1,adsfasdf
 
 `
-	verifyParsingError(t, csvTable, `cannot convert default value "zero" to type "long": strconv.ParseInt: parsing "zero": invalid syntax`, true)
+	verifyParsingError(t, csvTable, `cannot convert value "zero" in column of type "long" to Go type interface {} at line 5: strconv.ParseInt: parsing "zero": invalid syntax`, false)
 
 	csvTable = `#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string
 #group,false,false,true,true,false,false,true,true,true,true
@@ -1077,7 +963,7 @@ func TestFailedConversion(t *testing.T) {
 ,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,six,f,test,1,adsfasdf
 
 `
-	verifyParsingError(t, csvTable, `cannot convert value "seven" to type "double" at line 5: strconv.ParseFloat: parsing "seven": invalid syntax`, false)
+	verifyParsingError(t, csvTable, `cannot convert value "seven" in column of type "double" to Go type interface {} at line 5: strconv.ParseFloat: parsing "seven": invalid syntax`, false)
 }
 
 func TestDifferentNumberOfColumns(t *testing.T) {
@@ -1150,12 +1036,22 @@ func verifyParsingError(t *testing.T, csvTable, error string, inHeader bool) {
 
 	if inHeader {
 		require.False(t, res.NextSection())
+		require.False(t, res.NextRow())
+		require.Error(t, res.Err())
+		assert.Equal(t, error, res.Err().Error())
 	} else {
 		require.True(t, res.NextSection())
+		if res.NextRow() {
+			require.NoError(t, res.Err())
+			var row []interface{}
+			err := res.Decode(&row)
+			require.Error(t, err)
+			assert.Equal(t, error, err.Error())
+		} else {
+			require.Error(t, res.Err())
+			assert.Equal(t, error, res.Err().Error())
+		}
 	}
-	require.False(t, res.NextRow())
-	require.NotNil(t, res.Err())
-	assert.Equal(t, error, res.Err().Error())
 }
 
 func verifyTables(t *testing.T, csvTable string, expectedTables []expectedTable) {
@@ -1164,37 +1060,31 @@ func verifyTables(t *testing.T, csvTable string, expectedTables []expectedTable)
 
 	for _, table := range expectedTables {
 		require.True(t, res.NextSection(), res.Err())
-		require.Nil(t, res.Err())
+		require.NoError(t, res.Err())
 		require.Equal(t, table.columns, res.Columns())
 		for _, row := range table.rows {
 			require.True(t, res.NextRow(), res.Err())
-			require.Nil(t, res.Err())
-			for i, v := range res.Row() {
+			require.NoError(t, res.Err())
+			var r []interface{}
+			err := res.Decode(&r)
+			require.NoError(t, err)
+			for i, v := range r {
 				if table.columns[i].Type == "base64Binary" {
 					require.Equal(t, row[i], v)
 				} else {
 					require.True(t, row[i] == v, fmt.Sprintf("%v vs %v", row[i], v))
 				}
 			}
-			for i, c := range table.columns {
-				v := res.ValueByName(c.Name)
-				if c.Type == "base64Binary" {
-					require.Equal(t, row[i], v)
-				} else {
-					require.True(t, row[i] == v)
-				}
-			}
 		}
 		require.False(t, res.NextRow(), res.Err())
-		require.Nil(t, res.Err())
+		require.NoError(t, res.Err())
 	}
 
 	require.False(t, res.NextSection(), res.Err())
-	require.Nil(t, res.Err())
+	require.NoError(t, res.Err())
 
 	require.Nil(t, res.Columns())
 	require.Nil(t, res.Row())
-	require.Nil(t, res.ValueByName("table"))
 }
 
 // ExpectedTable represents table for comparison with parsed tables
diff --git a/inluxclient/query_test.go b/inluxclient/query_test.go
index 584d910..8ce9270 100644
--- a/inluxclient/query_test.go
+++ b/inluxclient/query_test.go
@@ -51,15 +51,15 @@ func TestMultiSections(t *testing.T) {
 		{ // Table 1
 			columns: []annotatedcsv.Column{
 				{Type: "string", Default: "_result", Name: "result", Group: false},
-				{Type: "long", Default: nil, Name: "table", Group: false},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_start", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_stop", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_time", Group: false},
-				{Type: "double", Default: nil, Name: "_value", Group: false},
-				{Type: "string", Default: nil, Name: "_field", Group: true},
-				{Type: "string", Default: nil, Name: "_measurement", Group: true},
-				{Type: "string", Default: nil, Name: "a", Group: true},
-				{Type: "string", Default: nil, Name: "b", Group: true},
+				{Type: "long", Default: "", Name: "table", Group: false},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_start", Group: true},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_stop", Group: true},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_time", Group: false},
+				{Type: "double", Default: "", Name: "_value", Group: false},
+				{Type: "string", Default: "", Name: "_field", Group: true},
+				{Type: "string", Default: "", Name: "_measurement", Group: true},
+				{Type: "string", Default: "", Name: "a", Group: true},
+				{Type: "string", Default: "", Name: "b", Group: true},
 			},
 			rows: [][]interface{}{
 				{"_result",
@@ -90,15 +90,15 @@ func TestMultiSections(t *testing.T) {
 		{ //Table 2
 			columns: []annotatedcsv.Column{
 				{Type: "string", Default: "_result", Name: "result", Group: false},
-				{Type: "long", Default: nil, Name: "table", Group: false},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_start", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_stop", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_time", Group: false},
-				{Type: "long", Default: nil, Name: "_value", Group: false},
-				{Type: "string", Default: nil, Name: "_field", Group: true},
-				{Type: "string", Default: nil, Name: "_measurement", Group: true},
-				{Type: "string", Default: nil, Name: "a", Group: true},
-				{Type: "string", Default: nil, Name: "b", Group: true},
+				{Type: "long", Default: "", Name: "table", Group: false},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_start", Group: true},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_stop", Group: true},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_time", Group: false},
+				{Type: "long", Default: "", Name: "_value", Group: false},
+				{Type: "string", Default: "", Name: "_field", Group: true},
+				{Type: "string", Default: "", Name: "_measurement", Group: true},
+				{Type: "string", Default: "", Name: "a", Group: true},
+				{Type: "string", Default: "", Name: "b", Group: true},
 			},
 			rows: [][]interface{}{
 				{"_result",
@@ -129,15 +129,15 @@ func TestMultiSections(t *testing.T) {
 		{ // Table 3
 			columns: []annotatedcsv.Column{
 				{Type: "string", Default: "_result", Name: "result", Group: false},
-				{Type: "long", Default: nil, Name: "table", Group: false},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_start", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_stop", Group: true},
-				{Type: "dateTime:RFC3339", Default: nil, Name: "_time", Group: false},
-				{Type: "boolean", Default: nil, Name: "_value", Group: false},
-				{Type: "string", Default: nil, Name: "_field", Group: true},
-				{Type: "string", Default: nil, Name: "_measurement", Group: true},
-				{Type: "string", Default: nil, Name: "a", Group: true},
-				{Type: "string", Default: nil, Name: "b", Group: true},
+				{Type: "long", Default: "", Name: "table", Group: false},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_start", Group: true},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_stop", Group: true},
+				{Type: "dateTime:RFC3339", Default: "", Name: "_time", Group: false},
+				{Type: "boolean", Default: "", Name: "_value", Group: false},
+				{Type: "string", Default: "", Name: "_field", Group: true},
+				{Type: "string", Default: "", Name: "_measurement", Group: true},
+				{Type: "string", Default: "", Name: "a", Group: true},
+				{Type: "string", Default: "", Name: "b", Group: true},
 			},
 			rows: [][]interface{}{
 				{"_result",
@@ -168,15 +168,15 @@ func TestMultiSections(t *testing.T) {
 		{ //Table 4
 			columns: []annotatedcsv.Column{
 				{Type: "string", Default: "_result", Name: "result", Group: false},
-				{Type: "long", Default: nil, Name: "table", Group: false},
-				{Type: "dateTime:RFC3339Nano", Default: nil, Name: "_start", Group: true},
-				{Type: "dateTime:RFC3339Nano", Default: nil, Name: "_stop", Group: true},
-				{Type: "dateTime:RFC3339Nano", Default: nil, Name: "_time", Group: false},
-				{Type: "unsignedLong", Default: nil, Name: "_value", Group: false},
-				{Type: "string", Default: nil, Name: "_field", Group: true},
-				{Type: "string", Default: nil, Name: "_measurement", Group: true},
-				{Type: "string", Default: nil, Name: "a", Group: true},
-				{Type: "string", Default: nil, Name: "b", Group: true},
+				{Type: "long", Default: "", Name: "table", Group: false},
+				{Type: "dateTime:RFC3339Nano", Default: "", Name: "_start", Group: true},
+				{Type: "dateTime:RFC3339Nano", Default: "", Name: "_stop", Group: true},
+				{Type: "dateTime:RFC3339Nano", Default: "", Name: "_time", Group: false},
+				{Type: "unsignedLong", Default: "", Name: "_value", Group: false},
+				{Type: "string", Default: "", Name: "_field", Group: true},
+				{Type: "string", Default: "", Name: "_measurement", Group: true},
+				{Type: "string", Default: "", Name: "a", Group: true},
+				{Type: "string", Default: "", Name: "b", Group: true},
 			},
 			rows: [][]interface{}{
 				{"_result",
@@ -215,8 +215,10 @@ func TestMultiSections(t *testing.T) {
 
 	//test skip first table header
 	require.True(t, res.NextRow())
-	require.Nil(t, res.Err())
-	require.Equal(t, tables[0].rows[0], res.Row())
+	require.NoError(t, res.Err())
+	var row []interface{}
+	require.NoError(t, res.Decode(&row))
+	require.Equal(t, tables[0].rows[0], row)
 	require.Nil(t, res.Close())
 
 	reader = strings.NewReader(csvTableMultiStructure)
@@ -224,16 +226,17 @@ func TestMultiSections(t *testing.T) {
 
 	//test skip tables
 	require.True(t, res.NextSection())
-	require.Nil(t, res.Err())
+	require.NoError(t, res.Err())
 	require.True(t, res.NextSection())
-	require.Nil(t, res.Err())
+	require.NoError(t, res.Err())
 	require.True(t, res.NextRow())
-	require.Nil(t, res.Err())
+	require.NoError(t, res.Err())
 	require.True(t, res.NextSection())
-	require.Nil(t, res.Err())
+	require.NoError(t, res.Err())
 	require.True(t, res.NextRow())
-	require.Nil(t, res.Err())
-	require.Equal(t, tables[2].rows[0], res.Row())
+	require.NoError(t, res.Err())
+	require.NoError(t, res.Decode(&row))
+	require.Equal(t, tables[2].rows[0], row)
 
 	require.Nil(t, res.Close())
 
@@ -257,17 +260,19 @@ func TestMultiSections(t *testing.T) {
 	//test skip first table header
 	require.True(t, res.NextRow())
 	require.True(t, res.NextRow())
-	require.Nil(t, res.Err())
-	require.Equal(t, tables[0].rows[1], res.Row())
+	require.NoError(t, res.Err())
+	row = row[:0]
+	require.NoError(t, res.Decode(&row))
+	require.Equal(t, tables[0].rows[1], row)
 	require.Nil(t, res.Close())
 
 	// skip sections
 	reader = strings.NewReader(csvTableMultiTables)
 	res = influxclient.NewQueryResultReader(ioutil.NopCloser(reader))
 	require.True(t, res.NextSection())
-	require.Nil(t, res.Err())
+	require.NoError(t, res.Err())
 	require.False(t, res.NextSection())
-	require.Nil(t, res.Err())
+	require.NoError(t, res.Err())
 	require.Nil(t, res.Close())
 }
 
@@ -279,7 +284,7 @@ func TestErrorInRow(t *testing.T) {
 ,failed to create physical plan: invalid time bounds from procedure from: bounds contain zero time,897
 
 `
-	verifyParsingError(t, csvTableError, "flux query error (code 897): failed to create physical plan: invalid time bounds from procedure from: bounds contain zero time", true)
+	verifyParsingError(t, csvTableError, "Flux query error (code 897): failed to create physical plan: invalid time bounds from procedure from: bounds contain zero time", true)
 
 	csvTableErrorNoReference := `#datatype,string,long
 #group,true,true
@@ -288,7 +293,7 @@ func TestErrorInRow(t *testing.T) {
 ,failed to create physical plan: invalid time bounds from procedure from: bounds contain zero time,
 
 `
-	verifyParsingError(t, csvTableErrorNoReference, "no row found in error section", true)
+	verifyParsingError(t, csvTableErrorNoReference, `cannot decode row in error section: cannot convert value "" in column of type "long" to Go type int64 at line 5: strconv.ParseInt: parsing "": invalid syntax`, true)
 
 	csvTableErrorNoMessage := `#datatype,string,long
 #group,true,true
@@ -314,7 +319,7 @@ func TestErrorInRow(t *testing.T) {
 ,failed to create physical plan: invalid time bounds from procedure from: bounds contain zero time,897
 
 `
-	verifyParsingError(t, csvTableErrorInvalidReferenceType, "unexpected column types (string, string) in error section", true)
+	verifyParsingError(t, csvTableErrorInvalidReferenceType, "cannot decode row in error section: cannot convert from column type string to int64", true)
 
 	csvTableErrorNoColumnReference := `#datatype,string
 #group,true
@@ -326,7 +331,7 @@ func TestErrorInRow(t *testing.T) {
 	tables := []expectedTable{
 		{ // Table 1
 			[]annotatedcsv.Column{
-				{Type: "string", Default: nil, Name: "error", Group: true},
+				{Type: "string", Default: "", Name: "error", Group: true},
 			},
 			[][]interface{}{
 				{
@@ -349,14 +354,14 @@ func TestCSVError(t *testing.T) {
 	res := influxclient.NewQueryResultReader(ioutil.NopCloser(reader))
 
 	require.False(t, res.NextSection())
-	require.NotNil(t, res.Err())
+	require.Error(t, res.Err())
 	require.Nil(t, res.Close())
 
 	reader = strings.NewReader(csvErrInHeader)
 	res = influxclient.NewQueryResultReader(ioutil.NopCloser(reader))
 	//straight to error
 	require.False(t, res.NextRow())
-	require.NotNil(t, res.Err())
+	require.Error(t, res.Err())
 	require.Nil(t, res.Close())
 
 	csvErrInRow := `#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string
@@ -370,10 +375,9 @@ func TestCSVError(t *testing.T) {
 
 	require.True(t, res.NextSection())
 	require.False(t, res.NextRow())
-	require.NotNil(t, res.Err())
+	require.Error(t, res.Err())
 	require.Nil(t, res.Close())
 }
-
 func verifyParsingError(t *testing.T, csvTable, error string, inHeader bool) {
 	reader := strings.NewReader(csvTable)
 	res := influxclient.NewQueryResultReader(ioutil.NopCloser(reader))
@@ -382,13 +386,23 @@ func verifyParsingError(t *testing.T, csvTable, error string, inHeader bool) {
 		require.False(t, res.NextSection())
 		//repeatedly
 		require.False(t, res.NextSection())
+		require.False(t, res.NextRow())
+		require.Error(t, res.Err())
+		assert.Equal(t, error, res.Err().Error())
 	} else {
 		require.True(t, res.NextSection())
+		if res.NextRow() {
+			require.NoError(t, res.Err())
+			var row []interface{}
+			err := res.Decode(&row)
+			require.Error(t, err)
+			assert.Equal(t, error, err.Error())
+		} else {
+			require.Error(t, res.Err())
+			assert.Equal(t, error, res.Err().Error())
+		}
 	}
-	require.False(t, res.NextRow())
-	require.NotNil(t, res.Err())
-	assert.Equal(t, error, res.Err().Error())
-	require.Nil(t, res.Close())
+	require.NoError(t, res.Close())
 }
 
 func verifyTables(t *testing.T, csvTable string, expectedTables []expectedTable) {
@@ -397,38 +411,32 @@ func verifyTables(t *testing.T, csvTable string, expectedTables []expectedTable)
 
 	for _, table := range expectedTables {
 		require.True(t, res.NextSection(), res.Err())
-		require.Nil(t, res.Err())
+		require.NoError(t, res.Err())
 		require.Equal(t, table.columns, res.Columns())
 		for _, row := range table.rows {
 			require.True(t, res.NextRow(), res.Err())
-			require.Nil(t, res.Err())
-			for i, v := range res.Row() {
+			require.NoError(t, res.Err())
+			var r []interface{}
+			err := res.Decode(&r)
+			require.NoError(t, err)
+			for i, v := range r {
 				if table.columns[i].Type == "base64Binary" {
 					require.Equal(t, row[i], v)
 				} else {
 					require.True(t, row[i] == v, fmt.Sprintf("%v vs %v", row[i], v))
 				}
 			}
-			for i, c := range table.columns {
-				v := res.ValueByName(c.Name)
-				if c.Type == "base64Binary" {
-					require.Equal(t, row[i], v)
-				} else {
-					require.True(t, row[i] == v)
-				}
-			}
 		}
 		require.False(t, res.NextRow(), res.Err())
-		require.Nil(t, res.Err())
+		require.NoError(t, res.Err())
 	}
 
 	require.False(t, res.NextSection(), res.Err())
-	require.Nil(t, res.Err())
+	require.NoError(t, res.Err())
 
 	require.Nil(t, res.Columns())
 	require.Nil(t, res.Row())
-	require.Nil(t, res.ValueByName("table"))
-	require.Nil(t, res.Close())
+	require.NoError(t, res.Close())
 }
 
 // ExpectedTable represents table for comparison with parsed tables
diff --git a/internal/reflect/visiblefields_test.go b/internal/reflect/visiblefields_test.go
new file mode 100644
index 0000000..8f881bf
--- /dev/null
+++ b/internal/reflect/visiblefields_test.go
@@ -0,0 +1,329 @@
+package reflect_test
+
+import (
+	. "reflect"
+	"testing"
+
+	"github.com/influxdata/influxdb-client-go/internal/reflect"
+)
+
+type structField struct {
+	name  string
+	index []int
+}
+
+var fieldsTests = []struct {
+	testName string
+	val      interface{}
+	expect   []structField
+}{{
+	testName: "SimpleStruct",
+	val: struct {
+		A int
+		B string
+		C bool
+	}{},
+	expect: []structField{{
+		name:  "A",
+		index: []int{0},
+	}, {
+		name:  "B",
+		index: []int{1},
+	}, {
+		name:  "C",
+		index: []int{2},
+	}},
+}, {
+	testName: "NonEmbeddedStructMember",
+	val: struct {
+		A struct {
+			X int
+		}
+	}{},
+	expect: []structField{{
+		name:  "A",
+		index: []int{0},
+	}},
+}, {
+	testName: "EmbeddedExportedStruct",
+	val: struct {
+		SFG
+	}{},
+	expect: []structField{{
+		name:  "SFG",
+		index: []int{0},
+	}, {
+		name:  "F",
+		index: []int{0, 0},
+	}, {
+		name:  "G",
+		index: []int{0, 1},
+	}},
+}, {
+	testName: "EmbeddedUnexportedStruct",
+	val: struct {
+		sFG
+	}{},
+	expect: []structField{{
+		name:  "sFG",
+		index: []int{0},
+	}, {
+		name:  "F",
+		index: []int{0, 0},
+	}, {
+		name:  "G",
+		index: []int{0, 1},
+	}},
+}, {
+	testName: "TwoEmbeddedStructsWithCancellingMembers",
+	val: struct {
+		SFG
+		SF
+	}{},
+	expect: []structField{{
+		name:  "SFG",
+		index: []int{0},
+	}, {
+		name:  "G",
+		index: []int{0, 1},
+	}, {
+		name:  "SF",
+		index: []int{1},
+	}},
+}, {
+	testName: "EmbeddedStructsWithSameFieldsAtDifferentDepths",
+	val: struct {
+		SFGH3
+		SG1
+		SFG2
+		SF2
+		L int
+	}{},
+	expect: []structField{{
+		name:  "SFGH3",
+		index: []int{0},
+	}, {
+		name:  "SFGH2",
+		index: []int{0, 0},
+	}, {
+		name:  "SFGH1",
+		index: []int{0, 0, 0},
+	}, {
+		name:  "SFGH",
+		index: []int{0, 0, 0, 0},
+	}, {
+		name:  "H",
+		index: []int{0, 0, 0, 0, 2},
+	}, {
+		name:  "SG1",
+		index: []int{1},
+	}, {
+		name:  "SG",
+		index: []int{1, 0},
+	}, {
+		name:  "G",
+		index: []int{1, 0, 0},
+	}, {
+		name:  "SFG2",
+		index: []int{2},
+	}, {
+		name:  "SFG1",
+		index: []int{2, 0},
+	}, {
+		name:  "SFG",
+		index: []int{2, 0, 0},
+	}, {
+		name:  "SF2",
+		index: []int{3},
+	}, {
+		name:  "SF1",
+		index: []int{3, 0},
+	}, {
+		name:  "SF",
+		index: []int{3, 0, 0},
+	}, {
+		name:  "L",
+		index: []int{4},
+	}},
+}, {
+	testName: "EmbeddedPointerStruct",
+	val: struct {
+		*SF
+	}{},
+	expect: []structField{{
+		name:  "SF",
+		index: []int{0},
+	}, {
+		name:  "F",
+		index: []int{0, 0},
+	}},
+}, {
+	testName: "EmbeddedNotAPointer",
+	val: struct {
+		M
+	}{},
+	expect: []structField{{
+		name:  "M",
+		index: []int{0},
+	}},
+}, {
+	testName: "RecursiveEmbedding",
+	val:      Rec1{},
+	expect: []structField{{
+		name:  "Rec2",
+		index: []int{0},
+	}, {
+		name:  "F",
+		index: []int{0, 0},
+	}, {
+		name:  "Rec1",
+		index: []int{0, 1},
+	}},
+}, {
+	testName: "RecursiveEmbedding2",
+	val:      Rec2{},
+	expect: []structField{{
+		name:  "F",
+		index: []int{0},
+	}, {
+		name:  "Rec1",
+		index: []int{1},
+	}, {
+		name:  "Rec2",
+		index: []int{1, 0},
+	}},
+}, {
+	testName: "RecursiveEmbedding3",
+	val:      RS3{},
+	expect: []structField{{
+		name:  "RS2",
+		index: []int{0},
+	}, {
+		name:  "RS1",
+		index: []int{1},
+	}, {
+		name:  "i",
+		index: []int{1, 0},
+	}},
+}}
+
+type SFG struct {
+	F int
+	G int
+}
+
+type SFG1 struct {
+	SFG
+}
+
+type SFG2 struct {
+	SFG1
+}
+
+type SFGH struct {
+	F int
+	G int
+	H int
+}
+
+type SFGH1 struct {
+	SFGH
+}
+
+type SFGH2 struct {
+	SFGH1
+}
+
+type SFGH3 struct {
+	SFGH2
+}
+
+type SF struct {
+	F int
+}
+
+type SF1 struct {
+	SF
+}
+
+type SF2 struct {
+	SF1
+}
+
+type SG struct {
+	G int
+}
+
+type SG1 struct {
+	SG
+}
+
+type sFG struct {
+	F int
+	G int
+}
+
+type RS1 struct {
+	//lint:ignore U1000 checked via reflection
+	i int
+}
+
+type RS2 struct {
+	RS1
+}
+
+type RS3 struct {
+	RS2
+	RS1
+}
+
+type M map[string]interface{}
+
+type Rec1 struct {
+	*Rec2
+}
+
+type Rec2 struct {
+	F string
+	*Rec1
+}
+
+func TestFields(t *testing.T) {
+	for _, test := range fieldsTests {
+		test := test
+		t.Run(test.testName, func(t *testing.T) {
+			typ := TypeOf(test.val)
+			fields := reflect.VisibleFields(typ)
+			if got, want := len(fields), len(test.expect); got != want {
+				t.Fatalf("unexpected field count; got %d want %d", got, want)
+			}
+
+			for j, field := range fields {
+				expect := test.expect[j]
+				t.Logf("field %d: %s", j, expect.name)
+				gotField := typ.FieldByIndex(field.Index)
+				// Unfortunately, FieldByIndex does not return
+				// a field with the same index that we passed in,
+				// so we set it to the expected value so that
+				// it can be compared later with the result of FieldByName.
+				gotField.Index = field.Index
+				expectField := typ.FieldByIndex(expect.index)
+				// ditto.
+				expectField.Index = expect.index
+				if !DeepEqual(gotField, expectField) {
+					t.Fatalf("unexpected field result\ngot %#v\nwant %#v", gotField, expectField)
+				}
+
+				// Sanity check that we can actually access the field by the
+				// expected name.
+				gotField1, ok := typ.FieldByName(expect.name)
+				if !ok {
+					t.Fatalf("field %q not accessible by name", expect.name)
+				}
+				if !DeepEqual(gotField1, expectField) {
+					t.Fatalf("unexpected FieldByName result; got %#v want %#v", gotField1, expectField)
+				}
+			}
+		})
+	}
+}