Repository: fxamacker/cbor
Pull request #31 (2019-11-15 00:44:20)

Update README.md
Tweaked summary to provide a bit more info.  Also use "Go's struct tags" instead of more complex name.


Patch:
diff --git a/BENCHMARKS.md b/BENCHMARKS.md
index f1d5c0f..9af3795 100644
--- a/BENCHMARKS.md
+++ b/BENCHMARKS.md
@@ -13,9 +13,9 @@ Benchmarks use data representing the following values:
 * Array: `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]`
 * Map: `{"a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "l": "L", "m": "M", "n": "N"}}`
 
-Go structs are faster than maps:
+Benchmarks data show:
 * decoding into struct is >66% faster than decoding into map.
-* encoding struct is >67% faster than encoding map.
+* encoding struct is >63% faster than encoding map.
 
 Decoding Benchmark | Time | Memory | Allocs 
 --- | ---: | ---: | ---:
@@ -40,12 +40,12 @@ BenchmarkUnmarshal/CBOR_map_to_Go_struct-2 | 1324 ns/op| 224 B/op | 2 allocs/op
 
 Encoding Benchmark | Time | Memory | Allocs 
 --- | ---: | ---: | ---:
-BenchmarkMarshal/Go_bool_to_CBOR_bool-2 | 88.5 ns/op	| 1 B/op | 1 allocs/op
-BenchmarkMarshal/Go_uint64_to_CBOR_positive_int-2 | 99.2 ns/op | 16 B/op | 1 allocs/op
-BenchmarkMarshal/Go_int64_to_CBOR_negative_int-2 | 92.7 ns/op | 3 B/op | 1 allocs/op
-BenchmarkMarshal/Go_float64_to_CBOR_float-2 | 97.2 ns/op	| 16 B/op | 1 allocs/op
-BenchmarkMarshal/Go_[]uint8_to_CBOR_bytes-2 | 121 ns/op | 32 B/op	| 1 allocs/op
+BenchmarkMarshal/Go_bool_to_CBOR_bool-2 | 93.8 ns/op	| 1 B/op | 1 allocs/op
+BenchmarkMarshal/Go_uint64_to_CBOR_positive_int-2 | 103 ns/op | 16 B/op | 1 allocs/op
+BenchmarkMarshal/Go_int64_to_CBOR_negative_int-2 | 96.9 ns/op | 3 B/op | 1 allocs/op
+BenchmarkMarshal/Go_float64_to_CBOR_float-2 | 101 ns/op	| 16 B/op | 1 allocs/op
+BenchmarkMarshal/Go_[]uint8_to_CBOR_bytes-2 | 140 ns/op | 32 B/op	| 1 allocs/op
 BenchmarkMarshal/Go_string_to_CBOR_text-2 | 121 ns/op | 48 B/op | 1 allocs/op
-BenchmarkMarshal/Go_[]int_to_CBOR_array-2 | 480 ns/op | 32 B/op	| 1 allocs/op
-BenchmarkMarshal/Go_map[string]string_to_CBOR_map-2 | 2194 ns/op | 576 B/op | 28 allocs/op
-BenchmarkMarshal/Go_struct_to_CBOR_map-2 | 714 ns/op | 64 B/op | 1 allocs/op
+BenchmarkMarshal/Go_[]int_to_CBOR_array-2 | 599 ns/op | 32 B/op	| 1 allocs/op
+BenchmarkMarshal/Go_map[string]string_to_CBOR_map-2 | 2211 ns/op | 576 B/op | 28 allocs/op
+BenchmarkMarshal/Go_struct_to_CBOR_map-2 | 809 ns/op | 64 B/op | 1 allocs/op
diff --git a/README.md b/README.md
index 6aac5cb..029f5c4 100644
--- a/README.md
+++ b/README.md
@@ -22,15 +22,15 @@
 # CBOR library in Go
 This library encodes and decodes CBOR ([RFC 7049](https://tools.ietf.org/html/rfc7049)).  CBOR is a concise binary alternative to JSON.
 
-:thumbsup: If you know Go's `encoding/json`, you already know how to use this library.
+:thumbsup: If you know Go's `encoding/json`, you already know how to use this library.  Existing structs don't require changes.  Go's struct tags like `` `cbor:"name,omitempty"` `` and `` `json:"name,omitempty"` `` both work as expected.
 
-:atom: It compiles to less than 0.5 MB and has no external dependencies.
+:atom: Your programs won't bloat.  This library compiles to under 0.5 MB, has no external dependencies, and no code gen.
 
 :lock: It avoids crashes on malicious CBOR data by using extensive tests, coverage-guided fuzzing, and data validation.
 
 :rocket: Starting in v1.3, faster speed became a high priority.  Faster libraries will always exist.  However, choose this one if you value your time, program size, and system reliability.
 
-Install with ```go get github.com/fxamacker/cbor``` and use it like Go's ```encoding/json```.  It supports `` `json:"name"` `` keys!
+Install with ```go get github.com/fxamacker/cbor``` and use it like Go's ```encoding/json```.
 
 ## Current Status
 Version 1.x has:
@@ -65,10 +65,10 @@ Libraries and programs were compiled for linux_amd64 using Go 1.12.
 
 ## Features
 * Idiomatic API like `encoding/json`.
+* Support "cbor" and "json" keys in Go's struct tags.
 * Decode slices, maps, and structs in-place.
 * Decode into struct with field name case-insensitive match.
 * Support canonical CBOR encoding for map/struct.
-* Support "cbor" and "json" keys for struct field format tags.
 * Encode anonymous struct fields by `encoding/json` package struct fields visibility rules.
 * Encode and decode nil slice/map/pointer/interface values correctly.
 * Encode and decode indefinite length bytes/string/array/map (["streaming"](https://tools.ietf.org/html/rfc7049#section-2.2)).
@@ -219,7 +219,7 @@ err = enc.EndIndefinite()
 ## Benchmarks
 In this library, Go structs are faster than maps:
 * decoding into struct is >66% faster than decoding into map.
-* encoding struct is >67% faster than encoding map.
+* encoding struct is >63% faster than encoding map.
 
 See [Benchmarks for fxamacker/cbor](BENCHMARKS.md).
 
diff --git a/cache.go b/cache.go
deleted file mode 100644
index 6ac81ba..0000000
--- a/cache.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package cbor
-
-import (
-	"bytes"
-	"reflect"
-	"sort"
-	"strconv"
-	"sync"
-)
-
-type encodingStructType struct {
-	fields          fields
-	canonicalFields fields
-	err             error
-}
-
-// byCanonicalRule sorts fields by field name length and field name.
-type byCanonicalRule struct {
-	fields
-}
-
-func (s byCanonicalRule) Less(i, j int) bool {
-	return bytes.Compare(s.fields[i].cborName, s.fields[j].cborName) <= 0
-}
-
-var (
-	decodingStructTypeCache sync.Map // map[reflect.Type]fields
-	encodingStructTypeCache sync.Map // map[reflect.Type]encodingStructType
-	encodingTypeCache       sync.Map // map[reflect.Type]encodeFunc
-)
-
-func getDecodingStructType(t reflect.Type) fields {
-	if v, _ := decodingStructTypeCache.Load(t); v != nil {
-		return v.(fields)
-	}
-	flds := getFields(t)
-	for i := 0; i < len(flds); i++ {
-		flds[i].isUnmarshaler = implementsUnmarshaler(flds[i].typ)
-	}
-	decodingStructTypeCache.Store(t, flds)
-	return flds
-}
-
-func getEncodingStructType(t reflect.Type) encodingStructType {
-	if v, _ := encodingStructTypeCache.Load(t); v != nil {
-		return v.(encodingStructType)
-	}
-
-	flds := getFields(t)
-
-	var err error
-	es := getEncodeState()
-	for i := 0; i < len(flds); i++ {
-		// Get field's encodeFunc
-		flds[i].ef = getEncodeFunc(flds[i].typ)
-		if flds[i].ef == nil {
-			err = &UnsupportedTypeError{t}
-			break
-		}
-
-		// Encode field name
-		if flds[i].keyasint {
-			nameAsInt, numErr := strconv.Atoi(flds[i].name)
-			if numErr != nil {
-				err = numErr
-				break
-			}
-			if nameAsInt >= 0 {
-				encodeTypeAndAdditionalValue(es, byte(cborTypePositiveInt), uint64(nameAsInt))
-			} else {
-				n := nameAsInt*(-1) - 1
-				encodeTypeAndAdditionalValue(es, byte(cborTypeNegativeInt), uint64(n))
-			}
-			flds[i].cborName = make([]byte, es.Len())
-			copy(flds[i].cborName, es.Bytes())
-			es.Reset()
-		} else {
-			encodeTypeAndAdditionalValue(es, byte(cborTypeTextString), uint64(len(flds[i].name)))
-			flds[i].cborName = make([]byte, es.Len()+len(flds[i].name))
-			n := copy(flds[i].cborName, es.Bytes())
-			copy(flds[i].cborName[n:], flds[i].name)
-			es.Reset()
-		}
-	}
-	putEncodeState(es)
-
-	if err != nil {
-		structType := encodingStructType{err: err}
-		encodingStructTypeCache.Store(t, structType)
-		return structType
-	}
-
-	// Sort fields by canonical order
-	canonicalFields := make(fields, len(flds))
-	copy(canonicalFields, flds)
-	sort.Sort(byCanonicalRule{canonicalFields})
-
-	structType := encodingStructType{fields: flds, canonicalFields: canonicalFields, err: err}
-	encodingStructTypeCache.Store(t, structType)
-	return structType
-}
-
-func getEncodeFunc(t reflect.Type) encodeFunc {
-	if v, _ := encodingTypeCache.Load(t); v != nil {
-		return v.(encodeFunc)
-	}
-	f := getEncodeFuncInternal(t)
-	encodingTypeCache.Store(t, f)
-	return f
-}
diff --git a/decode.go b/decode.go
index 9fea20b..34e1830 100644
--- a/decode.go
+++ b/decode.go
@@ -578,39 +578,13 @@ func (d *decodeState) parseMap(t cborType, count int, v reflect.Value) error {
 }
 
 func (d *decodeState) parseStruct(t cborType, count int, v reflect.Value) error {
-	flds := getDecodingStructType(v.Type())
+	flds := getStructFields(v.Type())
 	foundFlds := make([]bool, len(flds))
 
 	hasSize := count >= 0
 	for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
-		var keyBytes []byte
-		var err error
 		t := cborType(d.data[d.offset] & 0xE0)
-		if t == cborTypeTextString {
-			keyBytes, err = d.parseTextString()
-			if err != nil {
-				if d.err == nil {
-					d.err = err
-				}
-				d.skip() // skip value
-				continue
-			}
-		} else if t == cborTypePositiveInt || t == cborTypeNegativeInt {
-			v, err := d.parseInterface()
-			if err != nil {
-				if d.err == nil {
-					d.err = err
-				}
-				d.skip() // skip value
-				continue
-			}
-			switch n := v.(type) {
-			case int64:
-				keyBytes = []byte(strconv.Itoa(int(n)))
-			case uint64:
-				keyBytes = []byte(strconv.Itoa(int(n)))
-			}
-		} else {
+		if t != cborTypeTextString {
 			if d.err == nil {
 				d.err = &UnmarshalTypeError{Value: t.String(), Type: reflect.TypeOf(""), errMsg: "map key is of type " + t.String() + " and cannot be used to match struct " + v.Type().String() + " field name"}
 			}
@@ -618,6 +592,14 @@ func (d *decodeState) parseStruct(t cborType, count int, v reflect.Value) error
 			d.skip() // skip value
 			continue
 		}
+		keyBytes, err := d.parseTextString()
+		if err != nil {
+			if d.err == nil {
+				d.err = err
+			}
+			d.skip() // skip value
+			continue
+		}
 		keyLen := len(keyBytes)
 
 		var f *field
diff --git a/encode.go b/encode.go
index 68c2d47..3743c1b 100644
--- a/encode.go
+++ b/encode.go
@@ -64,24 +64,22 @@ type EncOptions struct {
 // An encodeState encodes CBOR into a bytes.Buffer.
 type encodeState struct {
 	bytes.Buffer
-	scratch [16]byte
+	scratch [64]byte
 }
 
 // encodeStatePool caches unused encodeState objects for later reuse.
 var encodeStatePool = sync.Pool{
 	New: func() interface{} {
-		es := new(encodeState)
-		es.Grow(32) // TODO: make this configurable
-		return es
+		return new(encodeState)
 	},
 }
 
-func getEncodeState() *encodeState {
+func newEncodeState() *encodeState {
 	return encodeStatePool.Get().(*encodeState)
 }
 
-// putEncodeState returns e to encodeStatePool.
-func putEncodeState(e *encodeState) {
+// returnEncodeState returns e to encodeStatePool.
+func returnEncodeState(e *encodeState) {
 	e.Reset()
 	encodeStatePool.Put(e)
 }
@@ -177,19 +175,12 @@ func encodeFloat(e *encodeState, v reflect.Value, opts EncOptions) (int, error)
 }
 
 func encodeByteString(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
-	if v.Kind() == reflect.Slice && v.IsNil() {
-		return e.Write(cborNil)
-	}
-	len := v.Len()
-	if len == 0 {
-		return 1, e.WriteByte(byte(cborTypeByteString))
-	}
-	n1 := encodeTypeAndAdditionalValue(e, byte(cborTypeByteString), uint64(len))
+	n1 := encodeTypeAndAdditionalValue(e, byte(cborTypeByteString), uint64(v.Len()))
 	if v.Kind() == reflect.Array {
-		for i := 0; i < len; i++ {
+		for i := 0; i < v.Len(); i++ {
 			e.WriteByte(byte(v.Index(i).Uint()))
 		}
-		return n1 + len, nil
+		return n1 + v.Len(), nil
 	}
 	n2, _ := e.Write(v.Bytes())
 	return n1 + n2, nil
@@ -205,24 +196,27 @@ func encodeStringInternal(e *encodeState, s string, opts EncOptions) (int, error
 	return n1 + n2, nil
 }
 
-type arrayEncoder struct {
-	f encodeFunc
+func encodeSlice(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
+	if v.IsNil() {
+		return e.Write(cborNil)
+	}
+	return encodeArray(e, v, opts)
 }
 
-func (ae arrayEncoder) encodeArray(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
-	if ae.f == nil {
-		return 0, &UnsupportedTypeError{v.Type()}
+func encodeArray(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
+	if v.Type().Elem().Kind() == reflect.Uint8 {
+		return encodeByteString(e, v, opts)
 	}
-	if v.Kind() == reflect.Slice && v.IsNil() {
-		return e.Write(cborNil)
+	f := getEncodeFunc(v.Type().Elem())
+	if f == nil {
+		return 0, &UnsupportedTypeError{v.Type()}
 	}
-	len := v.Len()
-	if len == 0 {
-		return 1, e.WriteByte(byte(cborTypeArray))
+	if v.Len() == 0 {
+		return encodeTypeAndAdditionalValue(e, byte(cborTypeArray), uint64(0)), nil
 	}
-	n := encodeTypeAndAdditionalValue(e, byte(cborTypeArray), uint64(len))
-	for i := 0; i < len; i++ {
-		n1, err := ae.f(e, v.Index(i), opts)
+	n := encodeTypeAndAdditionalValue(e, byte(cborTypeArray), uint64(v.Len()))
+	for i := 0; i < v.Len(); i++ {
+		n1, err := f(e, v.Index(i), opts)
 		if err != nil {
 			return 0, err
 		}
@@ -231,32 +225,29 @@ func (ae arrayEncoder) encodeArray(e *encodeState, v reflect.Value, opts EncOpti
 	return n, nil
 }
 
-type mapEncoder struct {
-	kf, ef encodeFunc
-}
-
-func (me mapEncoder) encodeMap(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
+func encodeMap(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
 	if opts.Canonical {
-		return me.encodeMapCanonical(e, v, opts)
+		return encodeMapCanonical(e, v, opts)
 	}
-	if me.kf == nil || me.ef == nil {
+	kf := getEncodeFunc(v.Type().Key())
+	ef := getEncodeFunc(v.Type().Elem())
+	if kf == nil || ef == nil {
 		return 0, &UnsupportedTypeError{v.Type()}
 	}
 	if v.IsNil() {
 		return e.Write(cborNil)
 	}
-	len := v.Len()
-	if len == 0 {
-		return 1, e.WriteByte(byte(cborTypeMap))
+	if v.Len() == 0 {
+		return encodeTypeAndAdditionalValue(e, byte(cborTypeMap), uint64(0)), nil
 	}
-	n := encodeTypeAndAdditionalValue(e, byte(cborTypeMap), uint64(len))
+	n := encodeTypeAndAdditionalValue(e, byte(cborTypeMap), uint64(v.Len()))
 	iter := v.MapRange()
 	for iter.Next() {
-		n1, err := me.kf(e, iter.Key(), opts)
+		n1, err := kf(e, iter.Key(), opts)
 		if err != nil {
 			return 0, err
 		}
-		n2, err := me.ef(e, iter.Value(), opts)
+		n2, err := ef(e, iter.Value(), opts)
 		if err != nil {
 			return 0, err
 		}
@@ -288,7 +279,7 @@ func (v byCanonical) Less(i, j int) bool {
 
 var byCanonicalPool = sync.Pool{}
 
-func getByCanonical(length int) *byCanonical {
+func newByCanonical(length int) *byCanonical {
 	v := byCanonicalPool.Get()
 	if v == nil {
 		return &byCanonical{pairs: make([]pair, 0, length)}
@@ -304,36 +295,38 @@ func getByCanonical(length int) *byCanonical {
 	return s
 }
 
-func putByCanonical(s *byCanonical) {
+func returnByCanonical(s *byCanonical) {
 	s.pairs = s.pairs[:0]
 	byCanonicalPool.Put(s)
 }
 
-func (me mapEncoder) encodeMapCanonical(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
-	if me.kf == nil || me.ef == nil {
+func encodeMapCanonical(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
+	kf := getEncodeFunc(v.Type().Key())
+	ef := getEncodeFunc(v.Type().Elem())
+	if kf == nil || ef == nil {
 		return 0, &UnsupportedTypeError{v.Type()}
 	}
 	if v.IsNil() {
 		return e.Write(cborNil)
 	}
 	if v.Len() == 0 {
-		return 1, e.WriteByte(byte(cborTypeMap))
+		return encodeTypeAndAdditionalValue(e, byte(cborTypeMap), uint64(0)), nil
 	}
-	pairEncodeState := getEncodeState() // accumulated cbor encoded map key-value pairs
-	pairs := getByCanonical(v.Len())    // for sorting keys
+	pairEncodeState := newEncodeState() // accumulated cbor encoded map key-value pairs
+	pairs := newByCanonical(v.Len())    // for sorting keys
 
 	iter := v.MapRange()
 	for iter.Next() {
-		n1, err := me.kf(pairEncodeState, iter.Key(), opts)
+		n1, err := kf(pairEncodeState, iter.Key(), opts)
 		if err != nil {
-			putEncodeState(pairEncodeState)
-			putByCanonical(pairs)
+			returnEncodeState(pairEncodeState)
+			returnByCanonical(pairs)
 			return 0, err
 		}
-		n2, err := me.ef(pairEncodeState, iter.Value(), opts)
+		n2, err := ef(pairEncodeState, iter.Value(), opts)
 		if err != nil {
-			putEncodeState(pairEncodeState)
-			putByCanonical(pairs)
+			returnEncodeState(pairEncodeState)
+			returnByCanonical(pairs)
 			return 0, err
 		}
 		pairs.pairs = append(pairs.pairs, pair{keyLen: n1, pairLen: n1 + n2})
@@ -353,48 +346,36 @@ func (me mapEncoder) encodeMapCanonical(e *encodeState, v reflect.Value, opts En
 		n += n1
 	}
 
-	putEncodeState(pairEncodeState)
-	putByCanonical(pairs)
+	returnEncodeState(pairEncodeState)
+	returnByCanonical(pairs)
 	return n, nil
 }
 
 func encodeStruct(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
-	structType := getEncodingStructType(v.Type())
-	if structType.err != nil {
-		return 0, structType.err
-	}
-
-	flds := structType.fields
-	if opts.Canonical {
-		flds = structType.canonicalFields
-	}
+	flds := getEncodingStructFields(v.Type(), opts.Canonical)
 
-	kve := getEncodeState() // encode key-value pairs based on struct field tag options
+	kve := newEncodeState() // encode key-value pairs based on struct field tag options
 	kvcount := 0
-	for i := 0; i < len(flds); i++ {
-		fv := v
-		ignore := false
-		for k, n := range flds[i].idx {
-			if k > 0 {
-				if fv.Kind() == reflect.Ptr && fv.Type().Elem().Kind() == reflect.Struct {
-					if fv.IsNil() {
-						// Null pointer to embedded struct
-						ignore = true
-						break
-					}
-					fv = fv.Elem()
-				}
-			}
-			fv = fv.Field(n)
+	for _, f := range flds {
+		if f.ef == nil {
+			return 0, &UnsupportedTypeError{v.Type()}
+		}
+		fv, err := fieldByIndex(v, f.idx)
+		if err != nil {
+			returnEncodeState(kve)
+			return 0, err
 		}
-		if ignore || (flds[i].omitempty && isEmptyValue(fv)) {
+		if !fv.IsValid() || (f.omitempty && isEmptyValue(fv)) {
 			continue
 		}
-
-		kve.Write(flds[i].cborName)
-
-		if _, err := flds[i].ef(kve, fv, opts); err != nil {
-			putEncodeState(kve)
+		if f.cborNameLen > 0 {
+			kve.Write(f.cborName[:f.cborNameLen])
+		} else {
+			encodeStringInternal(kve, f.name, opts)
+		}
+		_, err = f.ef(kve, fv, opts)
+		if err != nil {
+			returnEncodeState(kve)
 			return 0, err
 		}
 		kvcount++
@@ -403,7 +384,7 @@ func encodeStruct(e *encodeState, v reflect.Value, opts EncOptions) (int, error)
 	n := encodeTypeAndAdditionalValue(e, byte(cborTypeMap), uint64(kvcount))
 	n1, err := e.Write(kve.Bytes())
 
-	putEncodeState(kve)
+	returnEncodeState(kve)
 	return n + n1, err
 }
 
@@ -473,7 +454,7 @@ func getEncodeIndirectValueFunc(f encodeFunc) encodeFunc {
 	}
 }
 
-func getEncodeFuncInternal(t reflect.Type) encodeFunc {
+func getEncodeFunc(t reflect.Type) encodeFunc {
 	if t.Kind() == reflect.Ptr {
 		for t.Kind() == reflect.Ptr {
 			t = t.Elem()
@@ -504,13 +485,12 @@ func getEncodeFuncInternal(t reflect.Type) encodeFunc {
 		return encodeFloat
 	case reflect.String:
 		return encodeString
-	case reflect.Slice, reflect.Array:
-		if t.Elem().Kind() == reflect.Uint8 {
-			return encodeByteString
-		}
-		return arrayEncoder{f: getEncodeFunc(t.Elem())}.encodeArray
+	case reflect.Array:
+		return encodeArray
+	case reflect.Slice:
+		return encodeSlice
 	case reflect.Map:
-		return mapEncoder{kf: getEncodeFunc(t.Key()), ef: getEncodeFunc(t.Elem())}.encodeMap
+		return encodeMap
 	case reflect.Struct:
 		return encodeStruct
 	case reflect.Interface:
@@ -576,12 +556,8 @@ func isEmptyValue(v reflect.Value) bool {
 //
 // Marshal supports format string stored under the "cbor" key in the struct
 // field's tag.  CBOR format string can specify the name of the field, "omitempty"
-// and "keyasint" options, and special case "-" for field omission.  If "cbor"
-// key is absent, Marshal uses "json" key.
-//
-// Struct field name is treated as integer if it has "keyasint" option in
-// its format string.  The format string must specify an integer as its
-// field name.
+// option, and special case "-" for field omission.  if "cbor" key is absent,
+// Marshal uses "json" key.
 //
 // Anonymous struct fields are usually marshalled as if their exported fields
 // were fields in the outer struct.  Marshal follows the same struct fields
@@ -612,17 +588,17 @@ func isEmptyValue(v reflect.Value) bool {
 //
 // Marshal supports RFC 7049 and CTAP2 canonical CBOR encoding.
 func Marshal(v interface{}, encOpts EncOptions) ([]byte, error) {
-	e := getEncodeState()
+	e := newEncodeState()
 
 	err := e.marshal(v, encOpts)
 	if err != nil {
-		putEncodeState(e)
+		returnEncodeState(e)
 		return nil, err
 	}
 
 	buf := make([]byte, e.Len())
 	copy(buf, e.Bytes())
 
-	putEncodeState(e)
+	returnEncodeState(e)
 	return buf, nil
 }
diff --git a/structfields.go b/structfields.go
index 0569157..a5a3527 100644
--- a/structfields.go
+++ b/structfields.go
@@ -8,6 +8,7 @@ import (
 	"reflect"
 	"sort"
 	"strings"
+	"sync"
 )
 
 // fieldByIndex returns the nested field corresponding to the index.  It
@@ -32,14 +33,14 @@ func fieldByIndex(v reflect.Value, index []int) (reflect.Value, error) {
 
 type field struct {
 	name          string
-	cborName      []byte
 	idx           []int
 	typ           reflect.Type
-	ef            encodeFunc
 	isUnmarshaler bool
+	ef            encodeFunc
+	cborName      [64]byte
+	cborNameLen   int
 	tagged        bool // used to choose dominant field (at the same level tagged fields dominate untagged fields)
 	omitempty     bool // used to skip empty field
-	keyasint      bool // used to encode/decode field name as int
 }
 
 type fields []field
@@ -92,20 +93,35 @@ func (s byNameLevelAndTag) Less(i, j int) bool {
 	return i < j // Field i and j have the same name, depth, and tagged status. Nothing else matters.
 }
 
-func getFieldNameAndOptionsFromTag(tag string) (name string, omitEmpty bool, keyAsInt bool) {
+// byCanonicalRule sorts fields by field name length and field name.
+type byCanonicalRule struct {
+	fields
+}
+
+func (s byCanonicalRule) Less(i, j int) bool {
+	if len(s.fields[i].name) != len(s.fields[j].name) {
+		return len(s.fields[i].name) < len(s.fields[j].name)
+	}
+	return s.fields[i].name <= s.fields[j].name
+}
+
+func getFieldNameOptionsFromTag(tag string) (string, string) {
 	if len(tag) == 0 {
-		return
-	}
-	tokens := strings.Split(tag, ",")
-	name = tokens[0]
-	for _, s := range tokens[1:] {
-		if s == "omitempty" {
-			omitEmpty = true
-		} else if s == "keyasint" {
-			keyAsInt = true
-		}
+		return "", ""
+	}
+	commaIdx := strings.IndexByte(tag, byte(','))
+	if commaIdx == -1 {
+		return tag, ""
+	}
+	return tag[:commaIdx], tag[commaIdx+1:]
+}
+
+func hasFieldOptionFromTag(options, key string) bool {
+	idx := strings.Index(options, key)
+	if idx == -1 {
+		return false
 	}
-	return
+	return idx+len(key) == len(options) || options[idx+len(key)] == ','
 }
 
 // getFields returns a list of visible fields of struct type typ following Go
@@ -174,7 +190,8 @@ func getFields(typ reflect.Type) fields {
 				idx[len(fieldIdx)] = i
 
 				tagged := len(tag) > 0
-				tagFieldName, omitempty, keyasint := getFieldNameAndOptionsFromTag(tag)
+				tagFieldName, tagOptions := getFieldNameOptionsFromTag(tag)
+				omitempty := hasFieldOptionFromTag(tagOptions, "omitempty")
 
 				fieldName := tagFieldName
 				if tagFieldName == "" {
@@ -182,7 +199,7 @@ func getFields(typ reflect.Type) fields {
 				}
 
 				if !f.Anonymous || ft.Kind() != reflect.Struct || len(tagFieldName) > 0 {
-					flds = append(flds, field{name: fieldName, idx: idx, typ: f.Type, tagged: tagged, omitempty: omitempty, keyasint: keyasint})
+					flds = append(flds, field{name: fieldName, idx: idx, typ: f.Type, tagged: tagged, omitempty: omitempty})
 					continue
 				}
 
@@ -212,3 +229,64 @@ func getFields(typ reflect.Type) fields {
 
 	return visibleFields
 }
+
+type structFields struct {
+	typ             reflect.Type
+	fields          fields
+	canonicalFields fields
+}
+
+var (
+	cachedStructFields         sync.Map
+	cachedEncodingStructFields sync.Map
+)
+
+func getStructFields(t reflect.Type) fields {
+	if v, _ := cachedStructFields.Load(t); v != nil {
+		return v.(fields)
+	}
+	flds := getFields(t)
+	for i := 0; i < len(flds); i++ {
+		flds[i].isUnmarshaler = implementsUnmarshaler(flds[i].typ)
+	}
+	cachedStructFields.Store(t, flds)
+	return flds
+}
+
+func getEncodingStructFields(t reflect.Type, canonical bool) fields {
+	if v, _ := cachedEncodingStructFields.Load(t); v != nil {
+		if canonical {
+			return v.(structFields).canonicalFields
+		}
+		return v.(structFields).fields
+	}
+
+	fldsOrig := getStructFields(t)
+	flds := make(fields, len(fldsOrig))
+	copy(flds, fldsOrig)
+	for i := 0; i < len(flds); i++ {
+		flds[i].ef = getEncodeFunc(flds[i].typ)
+		nameLen := len(flds[i].name)
+		if nameLen <= 23 {
+			flds[i].cborName[0] = byte(cborTypeTextString) | byte(nameLen)
+			copy(flds[i].cborName[1:], flds[i].name)
+			flds[i].cborNameLen = 1 + nameLen
+		} else if nameLen <= 62 {
+			flds[i].cborName[0] = byte(cborTypeTextString) | byte(24)
+			flds[i].cborName[1] = byte(nameLen)
+			copy(flds[i].cborName[2:], flds[i].name)
+			flds[i].cborNameLen = 2 + nameLen
+		}
+	}
+
+	canonicalFields := make(fields, len(flds))
+	copy(canonicalFields, flds)
+	sort.Sort(byCanonicalRule{canonicalFields})
+
+	cachedEncodingStructFields.Store(t, structFields{typ: t, fields: flds, canonicalFields: canonicalFields})
+
+	if canonical {
+		return canonicalFields
+	}
+	return flds
+}


Test patch:
diff --git a/bench_test.go b/bench_test.go
index 45c8046..63c4ddd 100644
--- a/bench_test.go
+++ b/bench_test.go
@@ -149,42 +149,6 @@ func BenchmarkMarshal(b *testing.B) {
 	}
 }
 
-func BenchmarkMarshalCanonical(b *testing.B) {
-	for _, bm := range []struct {
-		name     string
-		cborData []byte
-		values   []interface{}
-	}{
-		{"map", hexDecode("ad616161416162614261636143616461446165614561666146616761476168614861696149616a614a616c614c616d614d616e614e"), []interface{}{map[string]string{"a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "l": "L", "m": "M", "n": "N"}, strc{A: "A", B: "B", C: "C", D: "D", E: "E", F: "F", G: "G", H: "H", I: "I", J: "J", L: "L", M: "M", N: "N"}}},
-	} {
-		for _, v := range bm.values {
-			name := "Go " + reflect.TypeOf(v).String() + " to CBOR " + bm.name
-			if reflect.TypeOf(v).Kind() == reflect.Struct {
-				name = "Go " + reflect.TypeOf(v).Kind().String() + " to CBOR " + bm.name
-			}
-			b.Run(name, func(b *testing.B) {
-				for i := 0; i < b.N; i++ {
-					if _, err := cbor.Marshal(v, cbor.EncOptions{}); err != nil {
-						b.Fatal("Marshal:", err)
-					}
-				}
-			})
-			// Canonical encoding
-			name = "Go " + reflect.TypeOf(v).String() + " to CBOR " + bm.name + " canonical"
-			if reflect.TypeOf(v).Kind() == reflect.Struct {
-				name = "Go " + reflect.TypeOf(v).Kind().String() + " to CBOR " + bm.name + " canonical"
-			}
-			b.Run(name, func(b *testing.B) {
-				for i := 0; i < b.N; i++ {
-					if _, err := cbor.Marshal(v, cbor.EncOptions{Canonical: true}); err != nil {
-						b.Fatal("Marshal:", err)
-					}
-				}
-			})
-		}
-	}
-}
-
 func BenchmarkEncode(b *testing.B) {
 	for _, bm := range encodeBenchmarks {
 		for _, v := range bm.values {
@@ -221,27 +185,13 @@ func BenchmarkEncodeStream(b *testing.B) {
 }
 
 func BenchmarkDecodeCOSE(b *testing.B) {
-	type coseKey struct {
-		Kty    int             `cbor:"1,keyasint"`
-		Alg    int             `cbor:"3,keyasint"`
-		CrvOrN cbor.RawMessage `cbor:"-1,keyasint"`
-		XOrE   cbor.RawMessage `cbor:"-2,keyasint"`
-		Y      cbor.RawMessage `cbor:"-3,keyasint"`
-	}
-
-	// Data from https://www.w3.org/TR/webauthn/#sec-attested-credential-data example 2, COSE_Key-encoded ES256 (P-256 curve) in EC2 format.
-	cborData := hexDecode("a501020326200121582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c")
+	cborData := hexDecode("a50102032620012158205af8047e9085ef79ec321280c7b95844d707d7fe4d73cd648f044c619ee74f6b22582036bb8c00768e90858012dc3831e15a389072bbdbe7e2e19155db9e1197655edf")
 	for _, ctor := range []func() interface{}{
 		func() interface{} { return new(interface{}) },
 		func() interface{} { return new(map[interface{}]interface{}) },
 		func() interface{} { return new(map[int]interface{}) },
-		func() interface{} { return new(coseKey) },
 	} {
-		t := reflect.TypeOf(ctor()).Elem()
-		name := "COSE to Go " + t.String()
-		if t.Kind() == reflect.Struct {
-			name = "COSE to Go " + t.Kind().String()
-		}
+		name := "COSE to Go " + reflect.TypeOf(ctor()).Elem().String()
 		b.Run(name, func(b *testing.B) {
 			for i := 0; i < b.N; i++ {
 				v := ctor()
diff --git a/decode_test.go b/decode_test.go
index ec5e15c..0964ded 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -1072,7 +1072,7 @@ func TestUnmarshalStructError2(t *testing.T) {
 	}
 
 	// Unmarshal returns first error encountered, which is *cbor.UnmarshalTypeError (failed to unmarshal int into Go string)
-	cborData := hexDecode("a3fa47c35000026161614161fe6142") // {100000.0:2, "a":"A", 0xfe: B}
+	cborData := hexDecode("a301026161614161fe6142") // {1:2, "a":"A", 0xfe: B}
 	v := strc{}
 	if err := cbor.Unmarshal(cborData, &v); err == nil {
 		t.Errorf("Unmarshal(0x%0x) doesn't return an error", cborData)
@@ -1080,14 +1080,14 @@ func TestUnmarshalStructError2(t *testing.T) {
 		if typeError, ok := err.(*cbor.UnmarshalTypeError); !ok {
 			t.Errorf("Unmarshal(0x%0x) returns wrong type of error %T, want (*cbor.UnmarshalTypeError)", cborData, err)
 		} else {
-			if typeError.Value != "primitives" {
-				t.Errorf("Unmarshal(0x%0x) (*cbor.UnmarshalTypeError).Value %s, want %s", cborData, typeError.Value, "primitives")
+			if typeError.Value != "positive integer" {
+				t.Errorf("Unmarshal(0x%0x) (*cbor.UnmarshalTypeError).Value %s, want %s", cborData, typeError.Value, "positive integer")
 			}
 			if typeError.Type != typeString {
 				t.Errorf("Unmarshal(0x%0x) (*cbor.UnmarshalTypeError).Type %s, want %s", cborData, typeError.Type, typeString)
 			}
-			if !strings.Contains(err.Error(), "cannot unmarshal primitives into Go value of type string") {
-				t.Errorf("Unmarshal(0x%0x) returns error %s, want error containing %q", cborData, err.Error(), "cannot unmarshal float into Go value of type string")
+			if !strings.Contains(err.Error(), "cannot unmarshal positive integer into Go value of type string") {
+				t.Errorf("Unmarshal(0x%0x) returns error %s, want error containing %q", cborData, err.Error(), "cannot unmarshal positive integer into Go value of type string")
 			}
 		}
 	}
@@ -1213,6 +1213,16 @@ func TestValid(t *testing.T) {
 	}
 }
 
+func TestSkipData(t *testing.T) {
+	cborData := hexDecode("850102031bffffffffffffffffc11a514b67b0") // [1, 2, 3, 18446744073709551615, 1363896240]
+	i := 0
+	if err := cbor.Unmarshal(cborData, &i); err == nil {
+		t.Errorf("Unmarshal(0x%0x) doesn't return error, want UnmarshalTypeError", cborData)
+	} else if _, ok := err.(*cbor.UnmarshalTypeError); !ok {
+		t.Errorf("Unmarshal(0x%0x) returns wrong type of error %T, want (*cbor.UnmarshalTypeError)", cborData, err)
+	}
+}
+
 func TestStructFieldNil(t *testing.T) {
 	type TestStruct struct {
 		I   int
@@ -1313,23 +1323,6 @@ func TestFuzzCrash5(t *testing.T) {
 	}
 }
 
-func TestFuzzCrash6(t *testing.T) {
-	// Crash5: parsing nil into map key.
-	testData := [][]byte{
-		[]byte("\x82\xa1\xf60000"),
-		[]byte("\xa1\xf600"),
-	}
-	for _, data := range testData {
-		var intf interface{}
-		if err := cbor.Unmarshal(data, &intf); err != nil {
-			t.Fatalf("Unmarshal(0x%02x) returns error %s\n", data, err)
-		}
-		if _, err := cbor.Marshal(intf, cbor.EncOptions{Canonical: true}); err != nil {
-			t.Errorf("Marshal(%v) returns error %s", intf, err)
-		}
-	}
-}
-
 func TestDecodeTime(t *testing.T) {
 	testCases := []struct {
 		name            string
@@ -1787,18 +1780,3 @@ func TestCOSEExamples(t *testing.T) {
 		}
 	}
 }
-
-func TestUnmarshalStructKeyAsIntError(t *testing.T) {
-	type T1 struct {
-		F1 int `cbor:"1,keyasint"`
-	}
-	cborData := hexDecode("a13bffffffffffffffff01") // {1: -18446744073709551616}
-	var v T1
-	if err := cbor.Unmarshal(cborData, &v); err == nil {
-		t.Errorf("Unmarshal(0x%0x) returns no error, %v (%T), want (*cbor.UnmarshalTypeError)", cborData, v, v)
-	} else if _, ok := err.(*cbor.UnmarshalTypeError); !ok {
-		t.Errorf("Unmarshal(0x%0x) returns wrong error %T, want (*cbor.UnmarshalTypeError)", cborData, err)
-	} else if !strings.Contains(err.Error(), "cannot unmarshal") {
-		t.Errorf("Unmarshal(0x%0x) returns error %s, want error containing %q", cborData, err, "cannot unmarshal")
-	}
-}
diff --git a/encode_test.go b/encode_test.go
index 58e76b0..143e7c3 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -9,7 +9,6 @@ import (
 	"io"
 	"math"
 	"reflect"
-	"strconv"
 	"strings"
 	"testing"
 	"time"
@@ -419,27 +418,6 @@ func encodeCborHeader(t byte, n uint64) []byte {
 	}
 }
 
-func testMarshal(t *testing.T, testCases []marshalTest) {
-	for _, tc := range testCases {
-		for _, value := range tc.values {
-			if _, err := cbor.Marshal(value, cbor.EncOptions{}); err != nil {
-				t.Errorf("Marshal(%v, cbor.EncOptions{}) returns error %v", value, err)
-			}
-			if b, err := cbor.Marshal(value, cbor.EncOptions{Canonical: true}); err != nil {
-				t.Errorf("Marshal(%v, cbor.EncOptions{Canonical: true}) returns error %v", value, err)
-			} else if !bytes.Equal(b, tc.cborData) {
-				t.Errorf("Marshal(%v, cbor.EncOptions{Canonical: true}) = 0x%0x, want 0x%0x", value, b, tc.cborData)
-			}
-		}
-		r := cbor.RawMessage(tc.cborData)
-		if b, err := cbor.Marshal(r, cbor.EncOptions{}); err != nil {
-			t.Errorf("Marshal(%v, cbor.EncOptions{}) returns error %v", r, err)
-		} else if !bytes.Equal(b, r) {
-			t.Errorf("Marshal(%v, cbor.EncOptions{}) returns %v, want %v", r, b, r)
-		}
-	}
-}
-
 func TestMarshalStruct(t *testing.T) {
 	v1 := outer{
 		IntField:          123,
@@ -556,43 +534,24 @@ func TestMarshalStructCanonical(t *testing.T) {
 	}
 }
 
-func TestMarshalNullPointerToEmbeddedStruct(t *testing.T) {
-	type (
-		T1 struct {
-			N int
-		}
-		T2 struct {
-			*T1
-		}
-	)
-	v := T2{}
-	wantCborData := []byte{0xa0} // {}
-	cborData, err := cbor.Marshal(v, cbor.EncOptions{})
-	if err != nil {
-		t.Fatalf("Marshal(%v) returns error %v", v, err)
-	}
-	if !bytes.Equal(wantCborData, cborData) {
-		t.Errorf("Marshal(%v) = 0x%0x, want 0x%0x", v, cborData, wantCborData)
-	}
-}
-
-func TestMarshalNullPointerToStruct(t *testing.T) {
-	type (
-		T1 struct {
-			N int
+func testMarshal(t *testing.T, testCases []marshalTest) {
+	for _, tc := range testCases {
+		for _, value := range tc.values {
+			if _, err := cbor.Marshal(value, cbor.EncOptions{}); err != nil {
+				t.Errorf("Marshal(%v, cbor.EncOptions{}) returns error %v", value, err)
+			}
+			if b, err := cbor.Marshal(value, cbor.EncOptions{Canonical: true}); err != nil {
+				t.Errorf("Marshal(%v, cbor.EncOptions{Canonical: true}) returns error %v", value, err)
+			} else if !bytes.Equal(b, tc.cborData) {
+				t.Errorf("Marshal(%v, cbor.EncOptions{Canonical: true}) = 0x%0x, want 0x%0x", value, b, tc.cborData)
+			}
 		}
-		T2 struct {
-			T *T1
+		r := cbor.RawMessage(tc.cborData)
+		if b, err := cbor.Marshal(r, cbor.EncOptions{}); err != nil {
+			t.Errorf("Marshal(%v, cbor.EncOptions{}) returns error %v", r, err)
+		} else if !bytes.Equal(b, r) {
+			t.Errorf("Marshal(%v, cbor.EncOptions{}) returns %v, want %v", r, b, r)
 		}
-	)
-	v := T2{}
-	wantCborData := []byte{0xa1, 0x61, 0x54, 0xf6} // {T: nil}
-	cborData, err := cbor.Marshal(v, cbor.EncOptions{})
-	if err != nil {
-		t.Fatalf("Marshal(%v) returns error %v", v, err)
-	}
-	if !bytes.Equal(wantCborData, cborData) {
-		t.Errorf("Marshal(%v) = 0x%0x, want 0x%0x", v, cborData, wantCborData)
 	}
 }
 
@@ -1368,91 +1327,3 @@ func TestMarshalRawMessageValue(t *testing.T) {
 		}
 	}
 }
-
-func TestCyclicDataStructure(t *testing.T) {
-	type Node struct {
-		V int   `cbor:"v"`
-		N *Node `cbor:"n,omitempty"`
-	}
-	v := Node{1, &Node{2, &Node{3, nil}}}                                                                                  // linked list: 1, 2, 3
-	wantCborData := []byte{0xa2, 0x61, 0x76, 0x01, 0x61, 0x6e, 0xa2, 0x61, 0x76, 0x02, 0x61, 0x6e, 0xa1, 0x61, 0x76, 0x03} // {v: 1, n: {v: 2, n: {v: 3}}}
-	cborData, err := cbor.Marshal(v, cbor.EncOptions{})
-	if err != nil {
-		t.Fatalf("Marshal(%v) returns error %s", v, err)
-	}
-	if !bytes.Equal(wantCborData, cborData) {
-		t.Errorf("Marshal(%v) = 0x%0x, want 0x%0x", v, cborData, wantCborData)
-	}
-	var v1 Node
-	if err = cbor.Unmarshal(cborData, &v1); err != nil {
-		t.Fatalf("Unmarshal(0x%0x) returns error %s", cborData, err)
-	}
-	if !reflect.DeepEqual(v, v1) {
-		t.Errorf("Unmarshal(0x%0x) returns %+v, want %+v", cborData, v1, v)
-	}
-}
-
-func TestMarshalUnmarshalStructKeyAsInt(t *testing.T) {
-	type T struct {
-		F1 int `cbor:"1,keyasint"`
-		F2 int `cbor:"2"`
-		F3 int `cbor:"-3,keyasint"`
-	}
-	v1 := T{F1: 1, F2: 2, F3: 3}
-	want := hexDecode("a301012203613202") // {1: 1, -3: 3, "2": 2}
-
-	b, err := cbor.Marshal(v1, cbor.EncOptions{Canonical: true})
-	if err != nil {
-		t.Errorf("Marshal(%+v) returns error %s", v1, err)
-	}
-	if !bytes.Equal(b, want) {
-		t.Errorf("Marshal(%+v) = 0x%0x, want 0x%0x", v1, b, want)
-	}
-
-	var v2 T
-	if err := cbor.Unmarshal(b, &v2); err != nil {
-		t.Errorf("Unmarshal(0x%0x) returns error %s", b, err)
-	}
-	if !reflect.DeepEqual(v1, v2) {
-		t.Errorf("Unmarshal(0x%0x) returns %+v, want %+v", b, v2, v1)
-	}
-}
-
-func TestMarshalStructKeyAsIntNumError(t *testing.T) {
-	type T1 struct {
-		F1 int `cbor:"2.0,keyasint"`
-	}
-	type T2 struct {
-		F1 int `cbor:"-18446744073709551616,keyasint"`
-	}
-	testCases := []struct {
-		name         string
-		obj          interface{}
-		wantErrorMsg string
-	}{
-		{
-			name:         "float as key",
-			obj:          T1{},
-			wantErrorMsg: "parsing \"2.0\": invalid syntax",
-		},
-		{
-			name:         "out of range int as key",
-			obj:          T2{},
-			wantErrorMsg: "parsing \"-18446744073709551616\": value out of range",
-		},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			b, err := cbor.Marshal(tc.obj, cbor.EncOptions{})
-			if err == nil {
-				t.Errorf("Marshal(%+v, cbor.EncOptions{}) doesn't return an error, want error %q", tc.obj, tc.wantErrorMsg)
-			} else if _, ok := err.(*strconv.NumError); !ok {
-				t.Errorf("Marshal(%v, cbor.EncOptions{}) error type %T, want *strconv.NumError", tc.obj, err)
-			} else if !strings.Contains(err.Error(), tc.wantErrorMsg) {
-				t.Errorf("Marshal(%v, cbor.EncOptions{}) error %s, want %s", tc.obj, err, tc.wantErrorMsg)
-			} else if b != nil {
-				t.Errorf("Marshal(%v, cbor.EncOptions{}) = 0x%0x, want nil", tc.obj, b)
-			}
-		})
-	}
-}