Repository: fxamacker/cbor (go)

Issue:
Properly handle empty values for custom types
Currently using `omitempty` with custom types doesn't work correctly due to [it being hardcoded](https://github.com/fxamacker/cbor/blob/bffaf0cda7c548af6e819cd5385f025bb6fee6f8/encode.go#L1329-L1345). For example, empty structs are still encoded as empty maps instead of omitting the field and fields for types implementing `encoding.BinaryMarshaler` are still included even when `MarshalBinary` returns an empty `[]byte`.

It seems that the `isEmptyValue` was copied from `encoding/json` but it behaving this way really makes it harder to support more compact encodings.

It would be great if one could override empty value handling for custom types or if existing empty value handling would behave correctly at least for the above two cases (empty structs and types implementing `encoding.BinaryMarshaler`).

Pull request:
Support omitempty for struct & BinaryMarshaler types
Struct field with "omitempty" option is omitted during encoding if:
- field is false
- field is 0
- field is empty string, array, slice, or map
- field is null pointer or interface
- field is nil or empty slice for cbor.RawMessage
- (new) field is nil or empty slice for types implementing cbor.BinaryMarshaler
- (new) field is empty struct

Currently "omitempty" option is a no-op for the following types:
- time.Time
- big.Int
- cbor.Tag
- cbor.RawTag
- types implementing cbor.Marshaler

Removed Go's copyright notice in encode.go.

Updated README and LICENSE to remove Go's copyright and license.

Closes #232

Patch:
diff --git a/LICENSE b/LICENSE
index b515f27..eaa8504 100644
--- a/LICENSE
+++ b/LICENSE
@@ -18,34 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-
----
-
-Portions (from Go) Copyright (c) 2009 The Go Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-   * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
-   * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
-   * Neither the name of Google Inc. nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
index c49e7da..b7e5268 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
 [![](https://github.com/fxamacker/cbor/workflows/linters/badge.svg)](https://github.com/fxamacker/cbor/actions?query=workflow%3Alinters)
 [![Go Report Card](https://goreportcard.com/badge/github.com/fxamacker/cbor)](https://goreportcard.com/report/github.com/fxamacker/cbor)
 [![](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/release_version_badge.svg?sanitize=1)](https://github.com/fxamacker/cbor/releases)
-<!--[![](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/license_badge.svg?sanitize=1)](https://raw.githubusercontent.com/fxamacker/cbor/master/LICENSE)-->
+[![](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/license_badge.svg?sanitize=1)](https://raw.githubusercontent.com/fxamacker/cbor/master/LICENSE)
 
 __fxamacker/cbor__ is secure.  It rejects malformed CBOR data, can detect duplicate map keys, and more.
 
@@ -1071,12 +1071,9 @@ __Words of encouragement and support__
 
 ## License 
 Copyright © 2019-present [Faye Amacker](https://github.com/fxamacker).  
-Portions (from [Go](https://github.com/golang/go)) Copyright © 2009 The Go Authors.
 
 fxamacker/cbor is licensed under the MIT License.  See [LICENSE](LICENSE) for the full license text.  
-[Go](https://github.com/golang/go) is licensed under BSD-style license.  See [LICENSE](LICENSE) for the full license text.
 
-This library contains a small amount of code from the [Go](https://github.com/golang/go) standard library.
 <hr>
 
 ⚓  [Quick Start](#quick-start) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Options](#options) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [License](#license)
diff --git a/cache.go b/cache.go
index e0cf2a9..3054bd4 100644
--- a/cache.go
+++ b/cache.go
@@ -13,10 +13,15 @@ import (
 	"sync"
 )
 
+type encodeFuncs struct {
+	ef  encodeFunc
+	ief isEmptyFunc
+}
+
 var (
 	decodingStructTypeCache sync.Map // map[reflect.Type]*decodingStructType
 	encodingStructTypeCache sync.Map // map[reflect.Type]*encodingStructType
-	encodeFuncCache         sync.Map // map[reflect.Type]encodeFunc
+	encodeFuncCache         sync.Map // map[reflect.Type]encodeFuncs
 	typeInfoCache           sync.Map // map[reflect.Type]*typeInfo
 )
 
@@ -108,13 +113,13 @@ func getDecodingStructType(t reflect.Type) *decodingStructType {
 }
 
 type encodingStructType struct {
-	fields            fields
-	bytewiseFields    fields
-	lengthFirstFields fields
-	err               error
-	toArray           bool
-	omitEmpty         bool
-	hasAnonymousField bool
+	fields             fields
+	bytewiseFields     fields
+	lengthFirstFields  fields
+	omitEmptyFieldsIdx []int
+	err                error
+	toArray            bool
+	fixedLength        bool // Struct type doesn't have any omitempty or anonymous fields.
 }
 
 func (st *encodingStructType) getFields(em *encMode) fields {
@@ -162,9 +167,10 @@ func (x *lengthFirstFieldSorter) Less(i, j int) bool {
 	return bytes.Compare(x.fields[i].cborName, x.fields[j].cborName) <= 0
 }
 
-func getEncodingStructType(t reflect.Type) *encodingStructType {
+func getEncodingStructType(t reflect.Type) (*encodingStructType, error) {
 	if v, _ := encodingStructTypeCache.Load(t); v != nil {
-		return v.(*encodingStructType)
+		structType := v.(*encodingStructType)
+		return structType, structType.err
 	}
 
 	flds, structOptions := getFields(t)
@@ -174,14 +180,14 @@ func getEncodingStructType(t reflect.Type) *encodingStructType {
 	}
 
 	var err error
-	var omitEmpty bool
-	var hasAnonymousField bool
 	var hasKeyAsInt bool
 	var hasKeyAsStr bool
+	var omitEmptyIdx []int
+	fixedLength := true
 	e := getEncoderBuffer()
 	for i := 0; i < len(flds); i++ {
 		// Get field's encodeFunc
-		flds[i].ef = getEncodeFunc(flds[i].typ)
+		flds[i].ef, flds[i].ief = getEncodeFunc(flds[i].typ)
 		if flds[i].ef == nil {
 			err = &UnsupportedTypeError{t}
 			break
@@ -218,12 +224,13 @@ func getEncodingStructType(t reflect.Type) *encodingStructType {
 
 		// Check if field is from embedded struct
 		if len(flds[i].idx) > 1 {
-			hasAnonymousField = true
+			fixedLength = false
 		}
 
 		// Check if field can be omitted when empty
 		if flds[i].omitEmpty {
-			omitEmpty = true
+			fixedLength = false
+			omitEmptyIdx = append(omitEmptyIdx, i)
 		}
 	}
 	putEncoderBuffer(e)
@@ -231,7 +238,7 @@ func getEncodingStructType(t reflect.Type) *encodingStructType {
 	if err != nil {
 		structType := &encodingStructType{err: err}
 		encodingStructTypeCache.Store(t, structType)
-		return structType
+		return structType, structType.err
 	}
 
 	// Sort fields by canonical order
@@ -247,49 +254,44 @@ func getEncodingStructType(t reflect.Type) *encodingStructType {
 	}
 
 	structType := &encodingStructType{
-		fields:            flds,
-		bytewiseFields:    bytewiseFields,
-		lengthFirstFields: lengthFirstFields,
-		omitEmpty:         omitEmpty,
-		hasAnonymousField: hasAnonymousField,
+		fields:             flds,
+		bytewiseFields:     bytewiseFields,
+		lengthFirstFields:  lengthFirstFields,
+		omitEmptyFieldsIdx: omitEmptyIdx,
+		fixedLength:        fixedLength,
 	}
 	encodingStructTypeCache.Store(t, structType)
-	return structType
+	return structType, structType.err
 }
 
-func getEncodingStructToArrayType(t reflect.Type, flds fields) *encodingStructType {
-	var hasAnonymousField bool
+func getEncodingStructToArrayType(t reflect.Type, flds fields) (*encodingStructType, error) {
 	for i := 0; i < len(flds); i++ {
 		// Get field's encodeFunc
-		flds[i].ef = getEncodeFunc(flds[i].typ)
+		flds[i].ef, flds[i].ief = getEncodeFunc(flds[i].typ)
 		if flds[i].ef == nil {
 			structType := &encodingStructType{err: &UnsupportedTypeError{t}}
 			encodingStructTypeCache.Store(t, structType)
-			return structType
-		}
-
-		// Check if field is from embedded struct
-		if len(flds[i].idx) > 1 {
-			hasAnonymousField = true
+			return structType, structType.err
 		}
 	}
 
 	structType := &encodingStructType{
-		fields:            flds,
-		toArray:           true,
-		hasAnonymousField: hasAnonymousField,
+		fields:      flds,
+		toArray:     true,
+		fixedLength: true,
 	}
 	encodingStructTypeCache.Store(t, structType)
-	return structType
+	return structType, structType.err
 }
 
-func getEncodeFunc(t reflect.Type) encodeFunc {
+func getEncodeFunc(t reflect.Type) (encodeFunc, isEmptyFunc) {
 	if v, _ := encodeFuncCache.Load(t); v != nil {
-		return v.(encodeFunc)
+		fs := v.(encodeFuncs)
+		return fs.ef, fs.ief
 	}
-	f := getEncodeFuncInternal(t)
-	encodeFuncCache.Store(t, f)
-	return f
+	ef, ief := getEncodeFuncInternal(t)
+	encodeFuncCache.Store(t, encodeFuncs{ef, ief})
+	return ef, ief
 }
 
 func getTypeInfo(t reflect.Type) *typeInfo {
diff --git a/encode.go b/encode.go
index 8737038..0f4a9d5 100644
--- a/encode.go
+++ b/encode.go
@@ -1,11 +1,6 @@
 // Copyright (c) Faye Amacker. All rights reserved.
 // Licensed under the MIT License. See LICENSE in the project root for license information.
 
-// This file contains some code from the Go standard library.
-
-// Portions (from Go) Copyright (c) 2009 The Go Authors.  Use of Go's source code
-// is governed by a BSD-style license.  See LICENSE for the full text of the license.
-
 package cbor
 
 import (
@@ -582,6 +577,7 @@ func putEncoderBuffer(e *encoderBuffer) {
 }
 
 type encodeFunc func(e *encoderBuffer, em *encMode, v reflect.Value) error
+type isEmptyFunc func(v reflect.Value) (empty bool, err error)
 
 var (
 	cborFalse            = []byte{0xf4}
@@ -599,7 +595,7 @@ func encode(e *encoderBuffer, em *encMode, v reflect.Value) error {
 		return nil
 	}
 	vt := v.Type()
-	f := getEncodeFunc(vt)
+	f, _ := getEncodeFunc(vt)
 	if f == nil {
 		return &UnsupportedTypeError{vt}
 	}
@@ -991,7 +987,18 @@ func (me mapEncodeFunc) encodeCanonical(e *encoderBuffer, em *encMode, v reflect
 	return nil
 }
 
-func encodeStructToArray(e *encoderBuffer, em *encMode, v reflect.Value, flds fields) (err error) {
+func encodeStructToArray(e *encoderBuffer, em *encMode, v reflect.Value) (err error) {
+	structType, err := getEncodingStructType(v.Type())
+	if err != nil {
+		return err
+	}
+
+	if b := em.encTagBytes(v.Type()); b != nil {
+		e.Write(b)
+	}
+
+	flds := structType.fields
+
 	encodeHead(e, byte(cborTypeArray), uint64(len(flds)))
 	for i := 0; i < len(flds); i++ {
 		f := flds[i]
@@ -1019,6 +1026,10 @@ func encodeStructToArray(e *encoderBuffer, em *encMode, v reflect.Value, flds fi
 }
 
 func encodeFixedLengthStruct(e *encoderBuffer, em *encMode, v reflect.Value, flds fields) error {
+	if b := em.encTagBytes(v.Type()); b != nil {
+		e.Write(b)
+	}
+
 	encodeHead(e, byte(cborTypeMap), uint64(len(flds)))
 
 	for i := 0; i < len(flds); i++ {
@@ -1035,23 +1046,14 @@ func encodeFixedLengthStruct(e *encoderBuffer, em *encMode, v reflect.Value, fld
 }
 
 func encodeStruct(e *encoderBuffer, em *encMode, v reflect.Value) (err error) {
-	vt := v.Type()
-	structType := getEncodingStructType(vt)
-	if structType.err != nil {
-		return structType.err
-	}
-
-	if b := em.encTagBytes(vt); b != nil {
-		e.Write(b)
-	}
-
-	if structType.toArray {
-		return encodeStructToArray(e, em, v, structType.fields)
+	structType, err := getEncodingStructType(v.Type())
+	if err != nil {
+		return err
 	}
 
 	flds := structType.getFields(em)
 
-	if !structType.hasAnonymousField && !structType.omitEmpty {
+	if structType.fixedLength {
 		return encodeFixedLengthStruct(e, em, v, flds)
 	}
 
@@ -1073,8 +1075,16 @@ func encodeStruct(e *encoderBuffer, em *encMode, v reflect.Value) (err error) {
 				continue
 			}
 		}
-		if f.omitEmpty && isEmptyValue(fv) {
-			continue
+
+		if f.omitEmpty {
+			empty, err := f.ief(fv)
+			if err != nil {
+				putEncoderBuffer(kve)
+				return err
+			}
+			if empty {
+				continue
+			}
 		}
 
 		kve.Write(f.cborName)
@@ -1086,6 +1096,10 @@ func encodeStruct(e *encoderBuffer, em *encMode, v reflect.Value) (err error) {
 		kvcount++
 	}
 
+	if b := em.encTagBytes(v.Type()); b != nil {
+		e.Write(b)
+	}
+
 	encodeHead(e, byte(cborTypeMap), uint64(kvcount))
 	e.Write(kve.Bytes())
 
@@ -1261,66 +1275,79 @@ func encodeHead(e *encoderBuffer, t byte, n uint64) {
 var (
 	typeMarshaler       = reflect.TypeOf((*Marshaler)(nil)).Elem()
 	typeBinaryMarshaler = reflect.TypeOf((*encoding.BinaryMarshaler)(nil)).Elem()
+	typeRawMessage      = reflect.TypeOf(RawMessage(nil))
 )
 
-func getEncodeFuncInternal(t reflect.Type) encodeFunc {
+func getEncodeFuncInternal(t reflect.Type) (encodeFunc, isEmptyFunc) {
 	k := t.Kind()
 	if k == reflect.Ptr {
-		return getEncodeIndirectValueFunc(t)
+		return getEncodeIndirectValueFunc(t), isEmptyPtr
 	}
 	switch t {
 	case typeTag:
-		return encodeTag
+		return encodeTag, alwaysNotEmpty
 	case typeTime:
-		return encodeTime
+		return encodeTime, alwaysNotEmpty
 	case typeBigInt:
-		return encodeBigInt
+		return encodeBigInt, alwaysNotEmpty
+	case typeRawMessage:
+		return encodeMarshalerType, isEmptySlice
 	}
 	if reflect.PtrTo(t).Implements(typeMarshaler) {
-		return encodeMarshalerType
+		return encodeMarshalerType, alwaysNotEmpty
 	}
 	if reflect.PtrTo(t).Implements(typeBinaryMarshaler) {
-		return encodeBinaryMarshalerType
+		return encodeBinaryMarshalerType, isEmptyBinaryMarshaler
 	}
 	switch k {
 	case reflect.Bool:
-		return encodeBool
+		return encodeBool, isEmptyBool
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		return encodeInt
+		return encodeInt, isEmptyInt
 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
-		return encodeUint
+		return encodeUint, isEmptyUint
 	case reflect.Float32, reflect.Float64:
-		return encodeFloat
+		return encodeFloat, isEmptyFloat
 	case reflect.String:
-		return encodeString
+		return encodeString, isEmptyString
 	case reflect.Slice, reflect.Array:
 		if t.Elem().Kind() == reflect.Uint8 {
-			return encodeByteString
+			return encodeByteString, isEmptySlice
 		}
-		f := getEncodeFunc(t.Elem())
+		f, _ := getEncodeFunc(t.Elem())
 		if f == nil {
-			return nil
+			return nil, nil
 		}
-		return arrayEncodeFunc{f: f}.encode
+		return arrayEncodeFunc{f: f}.encode, isEmptySlice
 	case reflect.Map:
-		kf, ef := getEncodeFunc(t.Key()), getEncodeFunc(t.Elem())
+		kf, _ := getEncodeFunc(t.Key())
+		ef, _ := getEncodeFunc(t.Elem())
 		if kf == nil || ef == nil {
-			return nil
+			return nil, nil
 		}
-		return mapEncodeFunc{kf: kf, ef: ef}.encode
+		return mapEncodeFunc{kf: kf, ef: ef}.encode, isEmptyMap
 	case reflect.Struct:
-		return encodeStruct
+		// Get struct's special field "_" tag options
+		if f, ok := t.FieldByName("_"); ok {
+			tag := f.Tag.Get("cbor")
+			if tag != "-" {
+				if hasToArrayOption(tag) {
+					return encodeStructToArray, isEmptyStruct
+				}
+			}
+		}
+		return encodeStruct, isEmptyStruct
 	case reflect.Interface:
-		return encodeIntf
+		return encodeIntf, isEmptyIntf
 	}
-	return nil
+	return nil, nil
 }
 
 func getEncodeIndirectValueFunc(t reflect.Type) encodeFunc {
 	for t.Kind() == reflect.Ptr {
 		t = t.Elem()
 	}
-	f := getEncodeFunc(t)
+	f, _ := getEncodeFunc(t)
 	if f == nil {
 		return nil
 	}
@@ -1336,24 +1363,101 @@ func getEncodeIndirectValueFunc(t reflect.Type) encodeFunc {
 	}
 }
 
-// isEmptyValue returns true if v has zero value for its type.
-// isEmptyValue was copied from stdlib's encoding/json/encode.go.
-func isEmptyValue(v reflect.Value) bool {
-	switch v.Kind() {
-	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
-		return v.Len() == 0
-	case reflect.Bool:
-		return !v.Bool()
-	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		return v.Int() == 0
-	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
-		return v.Uint() == 0
-	case reflect.Float32, reflect.Float64:
-		return v.Float() == 0
-	case reflect.Interface, reflect.Ptr:
-		return v.IsNil()
+func alwaysNotEmpty(v reflect.Value) (empty bool, err error) {
+	return false, nil
+}
+
+func isEmptyBool(v reflect.Value) (bool, error) {
+	return !v.Bool(), nil
+}
+
+func isEmptyInt(v reflect.Value) (bool, error) {
+	return v.Int() == 0, nil
+}
+
+func isEmptyUint(v reflect.Value) (bool, error) {
+	return v.Uint() == 0, nil
+}
+
+func isEmptyFloat(v reflect.Value) (bool, error) {
+	return v.Float() == 0.0, nil
+}
+
+func isEmptyString(v reflect.Value) (bool, error) {
+	return v.Len() == 0, nil
+}
+
+func isEmptySlice(v reflect.Value) (bool, error) {
+	return v.Len() == 0, nil
+}
+
+func isEmptyMap(v reflect.Value) (bool, error) {
+	return v.Len() == 0, nil
+}
+
+func isEmptyPtr(v reflect.Value) (bool, error) {
+	return v.IsNil(), nil
+}
+
+func isEmptyIntf(v reflect.Value) (bool, error) {
+	return v.IsNil(), nil
+}
+
+func isEmptyStruct(v reflect.Value) (bool, error) {
+	structType, err := getEncodingStructType(v.Type())
+	if err != nil {
+		return false, err
+	}
+
+	if structType.toArray {
+		return len(structType.fields) == 0, nil
+	}
+
+	if len(structType.fields) > len(structType.omitEmptyFieldsIdx) {
+		return false, nil
+	}
+
+	for _, i := range structType.omitEmptyFieldsIdx {
+		f := structType.fields[i]
+
+		// Get field value
+		var fv reflect.Value
+		if len(f.idx) == 1 {
+			fv = v.Field(f.idx[0])
+		} else {
+			// Get embedded field value.  No error is expected.
+			fv, _ = getFieldValue(v, f.idx, func(v reflect.Value) (reflect.Value, error) {
+				// Skip null pointer to embedded struct
+				return reflect.Value{}, nil
+			})
+			if !fv.IsValid() {
+				continue
+			}
+		}
+
+		empty, err := f.ief(fv)
+		if err != nil {
+			return false, err
+		}
+		if !empty {
+			return false, nil
+		}
+	}
+	return true, nil
+}
+
+func isEmptyBinaryMarshaler(v reflect.Value) (bool, error) {
+	m, ok := v.Interface().(encoding.BinaryMarshaler)
+	if !ok {
+		pv := reflect.New(v.Type())
+		pv.Elem().Set(v)
+		m = pv.Interface().(encoding.BinaryMarshaler)
+	}
+	data, err := m.MarshalBinary()
+	if err != nil {
+		return false, err
 	}
-	return false
+	return len(data) == 0, nil
 }
 
 func cannotFitFloat32(f64 float64) bool {
diff --git a/structfields.go b/structfields.go
index b33c051..7c5974f 100644
--- a/structfields.go
+++ b/structfields.go
@@ -16,6 +16,7 @@ type field struct {
 	idx       []int
 	typ       reflect.Type
 	ef        encodeFunc
+	ief       isEmptyFunc
 	typInfo   *typeInfo // used to decoder to reuse type info
 	tagged    bool      // used to choose dominant field (at the same level tagged fields dominate untagged fields)
 	omitEmpty bool      // used to skip empty field


Test patch:
diff --git a/decode_test.go b/decode_test.go
index e4d2933..5ffad86 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -2377,12 +2377,19 @@ func TestUnmarshalStructTag4(t *testing.T) {
 type number uint64
 
 func (n number) MarshalBinary() (data []byte, err error) {
+	if n == 0 {
+		return []byte{}, nil
+	}
 	data = make([]byte, 8)
 	binary.BigEndian.PutUint64(data, uint64(n))
 	return
 }
 
 func (n *number) UnmarshalBinary(data []byte) (err error) {
+	if len(data) == 0 {
+		*n = 0
+		return nil
+	}
 	if len(data) != 8 {
 		return errors.New("number:UnmarshalBinary: invalid length")
 	}
@@ -2395,10 +2402,17 @@ type stru struct {
 }
 
 func (s *stru) MarshalBinary() ([]byte, error) {
+	if s.a == "" && s.b == "" && s.c == "" {
+		return []byte{}, nil
+	}
 	return []byte(fmt.Sprintf("%s,%s,%s", s.a, s.b, s.c)), nil
 }
 
 func (s *stru) UnmarshalBinary(data []byte) (err error) {
+	if len(data) == 0 {
+		s.a, s.b, s.c = "", "", ""
+		return nil
+	}
 	ss := strings.Split(string(data), ",")
 	if len(ss) != 3 {
 		return errors.New("stru:UnmarshalBinary: invalid element count")
diff --git a/encode_test.go b/encode_test.go
index 7124fcb..7d2f278 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -967,74 +967,420 @@ func TestAnonymousFields11(t *testing.T) {
 	}
 }
 
-func TestAlwaysOmit(t *testing.T) {
+func TestOmitAndRenameStructField(t *testing.T) {
 	type T struct {
 		I   int // never omit
 		Io  int `cbor:",omitempty"` // omit empty
 		Iao int `cbor:"-"`          // always omit
+		R   int `cbor:"omitempty"`  // renamed to omitempty
 	}
 
-	v := T{}
-	want := []byte{0xa1, 0x61, 0x49, 0x00} // {"I": 0}
-	b, err := Marshal(v)
-	if err != nil {
-		t.Errorf("Marshal(%v) returned error %v", v, err)
-	} else if !bytes.Equal(b, want) {
-		t.Errorf("Marshal(%v) = 0x%x, want 0x%x", v, b, want)
-	}
+	v1 := T{}
+	// {"I": 0, "omitempty": 0}
+	want1 := []byte{0xa2,
+		0x61, 0x49, 0x00,
+		0x69, 0x6f, 0x6d, 0x69, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x00}
 
-	v = T{I: 1, Io: 2, Iao: 3}
-	want = []byte{0xa2, 0x61, 0x49, 0x01, 0x62, 0x49, 0x6f, 0x02} // {"I": 1, "Io": 2}
-	b, err = Marshal(v)
-	if err != nil {
-		t.Errorf("Marshal(%v) returned error %v", v, err)
-	} else if !bytes.Equal(b, want) {
-		t.Errorf("Marshal(%v) = 0x%x, want 0x%x", v, b, want)
-	}
+	v2 := T{I: 1, Io: 2, Iao: 0, R: 3}
+	// {"I": 1, "Io": 2, "omitempty": 3}
+	want2 := []byte{0xa3,
+		0x61, 0x49, 0x01,
+		0x62, 0x49, 0x6f, 0x02,
+		0x69, 0x6f, 0x6d, 0x69, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x03}
+
+	em, _ := EncOptions{}.EncMode()
+	dm, _ := DecOptions{}.DecMode()
+	tests := []roundTripTest{
+		{"default values", v1, want1},
+		{"non-default values", v2, want2}}
+	testRoundTrip(t, tests, em, dm)
 }
 
-func TestOmitEmpty(t *testing.T) {
+func TestOmitEmptyForBuiltinType(t *testing.T) {
 	type T struct {
-		B    bool                   `cbor:"b"`
-		Bo   bool                   `cbor:"bo,omitempty"`
-		UI   uint                   `cbor:"ui"`
-		UIo  uint                   `cbor:"uio,omitempty"`
-		I    int                    `cbor:"omitempty"` // actually named omitempty, not an option
-		Io   int                    `cbor:"io,omitempty"`
-		F    float64                `cbor:"f"`
-		Fo   float64                `cbor:"fo,omitempty"`
-		S    string                 `cbor:"s"`
-		So   string                 `cbor:"so,omitempty"`
-		Slc  []string               `cbor:"slc"`
-		Slco []string               `cbor:"slco,omitempty"`
-		M    map[string]interface{} `cbor:"m"`
-		Mo   map[string]interface{} `cbor:"mo,omitempty"`
-		Str  struct{}               `cbor:"str"`
-		Stro struct{}               `cbor:"stro,omitempty"`
-		P    *int                   `cbor:"p"`
-		Po   *int                   `cbor:"po,omitempty"`
-	}
-	// {"b": false, "ui": 0, "omitempty": 0, "f": 0, "s": "", "slc": null, "m": {}, "str": {}, "stro": {}, "p": nil }
-	want := []byte{0xaa,
+		B     bool           `cbor:"b"`
+		Bo    bool           `cbor:"bo,omitempty"`
+		UI    uint           `cbor:"ui"`
+		UIo   uint           `cbor:"uio,omitempty"`
+		I     int            `cbor:"i"`
+		Io    int            `cbor:"io,omitempty"`
+		F     float64        `cbor:"f"`
+		Fo    float64        `cbor:"fo,omitempty"`
+		S     string         `cbor:"s"`
+		So    string         `cbor:"so,omitempty"`
+		Slc   []string       `cbor:"slc"`
+		Slco  []string       `cbor:"slco,omitempty"`
+		M     map[int]string `cbor:"m"`
+		Mo    map[int]string `cbor:"mo,omitempty"`
+		P     *int           `cbor:"p"`
+		Po    *int           `cbor:"po,omitempty"`
+		Intf  interface{}    `cbor:"intf"`
+		Intfo interface{}    `cbor:"intfo,omitempty"`
+	}
+
+	v := T{}
+	// {"b": false, "ui": 0, "i":0, "f": 0, "s": "", "slc": null, "m": {}, "p": nil, "intf": nil }
+	want := []byte{0xa9,
 		0x61, 0x62, 0xf4,
 		0x62, 0x75, 0x69, 0x00,
-		0x69, 0x6f, 0x6d, 0x69, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x00,
+		0x61, 0x69, 0x00,
 		0x61, 0x66, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 		0x61, 0x73, 0x60,
 		0x63, 0x73, 0x6c, 0x63, 0xf6,
 		0x61, 0x6d, 0xf6,
-		0x63, 0x73, 0x74, 0x72, 0xa0,
-		0x64, 0x73, 0x74, 0x72, 0x6f, 0xa0,
 		0x61, 0x70, 0xf6,
+		0x64, 0x69, 0x6e, 0x74, 0x66, 0xf6,
 	}
 
-	var v T
-	b, err := Marshal(v)
-	if err != nil {
-		t.Errorf("Marshal(%v) returned error %v", v, err)
-	} else if !bytes.Equal(b, want) {
-		t.Errorf("Marshal(%v) = 0x%x, want 0x%x", v, b, want)
+	em, _ := EncOptions{}.EncMode()
+	dm, _ := DecOptions{}.DecMode()
+	testRoundTrip(t, []roundTripTest{{"default values", v, want}}, em, dm)
+}
+
+func TestOmitEmptyForAnonymousStruct(t *testing.T) {
+	type T struct {
+		Str  struct{} `cbor:"str"`
+		Stro struct{} `cbor:"stro,omitempty"`
+	}
+
+	v := T{}
+	want := []byte{0xa1, 0x63, 0x73, 0x74, 0x72, 0xa0} // {"str": {}}
+
+	em, _ := EncOptions{}.EncMode()
+	dm, _ := DecOptions{}.DecMode()
+	testRoundTrip(t, []roundTripTest{{"default values", v, want}}, em, dm)
+}
+
+func TestOmitEmptyForStruct1(t *testing.T) {
+	type T1 struct {
+		Bo    bool           `cbor:"bo,omitempty"`
+		UIo   uint           `cbor:"uio,omitempty"`
+		Io    int            `cbor:"io,omitempty"`
+		Fo    float64        `cbor:"fo,omitempty"`
+		So    string         `cbor:"so,omitempty"`
+		Slco  []string       `cbor:"slco,omitempty"`
+		Mo    map[int]string `cbor:"mo,omitempty"`
+		Po    *int           `cbor:"po,omitempty"`
+		Intfo interface{}    `cbor:"intfo,omitempty"`
+	}
+	type T struct {
+		Str  T1 `cbor:"str"`
+		Stro T1 `cbor:"stro,omitempty"`
+	}
+
+	v := T{}
+	want := []byte{0xa1, 0x63, 0x73, 0x74, 0x72, 0xa0} // {"str": {}}
+
+	em, _ := EncOptions{}.EncMode()
+	dm, _ := DecOptions{}.DecMode()
+	testRoundTrip(t, []roundTripTest{{"default values", v, want}}, em, dm)
+}
+
+func TestOmitEmptyForStruct2(t *testing.T) {
+	type T1 struct {
+		Bo    bool           `cbor:"bo,omitempty"`
+		UIo   uint           `cbor:"uio,omitempty"`
+		Io    int            `cbor:"io,omitempty"`
+		Fo    float64        `cbor:"fo,omitempty"`
+		So    string         `cbor:"so,omitempty"`
+		Slco  []string       `cbor:"slco,omitempty"`
+		Mo    map[int]string `cbor:"mo,omitempty"`
+		Po    *int           `cbor:"po,omitempty"`
+		Intfo interface{}    `cbor:"intfo"`
+	}
+	type T struct {
+		Stro T1 `cbor:"stro,omitempty"`
+	}
+
+	v := T{}
+	want := []byte{0xa1, 0x64, 0x73, 0x74, 0x72, 0x6f, 0xa1, 0x65, 0x69, 0x6e, 0x74, 0x66, 0x6f, 0xf6} // {"stro": {intfo: nil}}
+
+	em, _ := EncOptions{}.EncMode()
+	dm, _ := DecOptions{}.DecMode()
+	testRoundTrip(t, []roundTripTest{{"non-default values", v, want}}, em, dm)
+}
+
+func TestOmitEmptyForNestedStruct(t *testing.T) {
+	type T1 struct {
+		Bo    bool           `cbor:"bo,omitempty"`
+		UIo   uint           `cbor:"uio,omitempty"`
+		Io    int            `cbor:"io,omitempty"`
+		Fo    float64        `cbor:"fo,omitempty"`
+		So    string         `cbor:"so,omitempty"`
+		Slco  []string       `cbor:"slco,omitempty"`
+		Mo    map[int]string `cbor:"mo,omitempty"`
+		Po    *int           `cbor:"po,omitempty"`
+		Intfo interface{}    `cbor:"intfo,omitempty"`
+	}
+	type T2 struct {
+		Stro T1 `cbor:"stro,omitempty"`
+	}
+	type T struct {
+		Str  T2 `cbor:"str"`
+		Stro T2 `cbor:"stro,omitempty"`
+	}
+
+	v := T{}
+	want := []byte{0xa1, 0x63, 0x73, 0x74, 0x72, 0xa0} // {"str": {}}
+
+	em, _ := EncOptions{}.EncMode()
+	dm, _ := DecOptions{}.DecMode()
+	testRoundTrip(t, []roundTripTest{{"default values", v, want}}, em, dm)
+}
+
+func TestOmitEmptyForToArrayStruct1(t *testing.T) {
+	type T1 struct {
+		_    struct{} `cbor:",toarray"`
+		b    bool
+		ui   uint
+		i    int
+		f    float64
+		s    string
+		slc  []string
+		m    map[int]string
+		p    *int
+		intf interface{}
+	}
+	type T struct {
+		Str  T1 `cbor:"str"`
+		Stro T1 `cbor:"stro,omitempty"`
 	}
+
+	v := T{
+		Str:  T1{b: false, ui: 0, i: 0, f: 0.0, s: "", slc: nil, m: nil, p: nil, intf: nil},
+		Stro: T1{b: false, ui: 0, i: 0, f: 0.0, s: "", slc: nil, m: nil, p: nil, intf: nil},
+	}
+	want := []byte{0xa1, 0x63, 0x73, 0x74, 0x72, 0x80} // {"str": []}
+
+	em, _ := EncOptions{}.EncMode()
+	dm, _ := DecOptions{}.DecMode()
+	testRoundTrip(t, []roundTripTest{{"no exportable fields", v, want}}, em, dm)
+}
+
+func TestOmitEmptyForToArrayStruct2(t *testing.T) {
+	type T1 struct {
+		_     struct{}       `cbor:",toarray"`
+		Bo    bool           `cbor:"bo"`
+		UIo   uint           `cbor:"uio"`
+		Io    int            `cbor:"io"`
+		Fo    float64        `cbor:"fo"`
+		So    string         `cbor:"so"`
+		Slco  []string       `cbor:"slco"`
+		Mo    map[int]string `cbor:"mo"`
+		Po    *int           `cbor:"po"`
+		Intfo interface{}    `cbor:"intfo"`
+	}
+	type T struct {
+		Stro T1 `cbor:"stro,omitempty"`
+	}
+
+	v := T{}
+	// {"stro": [false, 0, 0, 0.0, "", [], {}, nil, nil]}
+	want := []byte{0xa1, 0x64, 0x73, 0x74, 0x72, 0x6f, 0x89, 0xf4, 0x00, 0x00, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xf6, 0xf6, 0xf6, 0xf6}
+
+	em, _ := EncOptions{}.EncMode()
+	dm, _ := DecOptions{}.DecMode()
+	testRoundTrip(t, []roundTripTest{{"has exportable fields", v, want}}, em, dm)
+}
+
+func TestOmitEmptyForStructWithPtrToAnonymousField(t *testing.T) {
+	type (
+		T1 struct {
+			X int `cbor:"x,omitempty"`
+			Y int `cbor:"y,omitempty"`
+		}
+		T2 struct {
+			*T1
+		}
+		T struct {
+			Stro T2 `cbor:"stro,omitempty"`
+		}
+	)
+
+	testCases := []struct {
+		name         string
+		obj          interface{}
+		wantCborData []byte
+	}{
+		{
+			name:         "null pointer to anonymous field",
+			obj:          T{},
+			wantCborData: []byte{0xa0}, // {}
+		},
+		{
+			name:         "not-null pointer to anonymous field",
+			obj:          T{T2{&T1{}}},
+			wantCborData: []byte{0xa0}, // {}
+		},
+		{
+			name:         "not empty value in field 1",
+			obj:          T{T2{&T1{X: 1}}},
+			wantCborData: []byte{0xa1, 0x64, 0x73, 0x74, 0x72, 0x6f, 0xa1, 0x61, 0x78, 0x01}, // {stro:{x:1}}
+		},
+		{
+			name:         "not empty value in field 2",
+			obj:          T{T2{&T1{Y: 2}}},
+			wantCborData: []byte{0xa1, 0x64, 0x73, 0x74, 0x72, 0x6f, 0xa1, 0x61, 0x79, 0x02}, // {stro:{y:2}}
+		},
+		{
+			name:         "not empty value in all fields",
+			obj:          T{T2{&T1{X: 1, Y: 2}}},
+			wantCborData: []byte{0xa1, 0x64, 0x73, 0x74, 0x72, 0x6f, 0xa2, 0x61, 0x78, 0x01, 0x61, 0x79, 0x02}, // {stro:{x:1, y:2}}
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			b, err := Marshal(tc.obj)
+			if err != nil {
+				t.Errorf("Marshal(%+v) returned error %v", tc.obj, err)
+			}
+			if !bytes.Equal(b, tc.wantCborData) {
+				t.Errorf("Marshal(%+v) = 0x%x, want 0x%x", tc.obj, b, tc.wantCborData)
+			}
+		})
+	}
+}
+
+func TestOmitEmptyForStructWithAnonymousField(t *testing.T) {
+	type (
+		T1 struct {
+			X int `cbor:"x,omitempty"`
+			Y int `cbor:"y,omitempty"`
+		}
+		T2 struct {
+			T1
+		}
+		T struct {
+			Stro T2 `cbor:"stro,omitempty"`
+		}
+	)
+
+	testCases := []struct {
+		name         string
+		obj          interface{}
+		wantCborData []byte
+	}{
+		{
+			name:         "default values",
+			obj:          T{},
+			wantCborData: []byte{0xa0}, // {}
+		},
+		{
+			name:         "default values",
+			obj:          T{T2{T1{}}},
+			wantCborData: []byte{0xa0}, // {}
+		},
+		{
+			name:         "not empty value in field 1",
+			obj:          T{T2{T1{X: 1}}},
+			wantCborData: []byte{0xa1, 0x64, 0x73, 0x74, 0x72, 0x6f, 0xa1, 0x61, 0x78, 0x01}, // {stro:{x:1}}
+		},
+		{
+			name:         "not empty value in field 2",
+			obj:          T{T2{T1{Y: 2}}},
+			wantCborData: []byte{0xa1, 0x64, 0x73, 0x74, 0x72, 0x6f, 0xa1, 0x61, 0x79, 0x02}, // {stro:{y:2}}
+		},
+		{
+			name:         "not empty value in all fields",
+			obj:          T{T2{T1{X: 1, Y: 2}}},
+			wantCborData: []byte{0xa1, 0x64, 0x73, 0x74, 0x72, 0x6f, 0xa2, 0x61, 0x78, 0x01, 0x61, 0x79, 0x02}, // {stro:{x:1, y:2}}
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			b, err := Marshal(tc.obj)
+			if err != nil {
+				t.Errorf("Marshal(%+v) returned error %v", tc.obj, err)
+			}
+			if !bytes.Equal(b, tc.wantCborData) {
+				t.Errorf("Marshal(%+v) = 0x%x, want 0x%x", tc.obj, b, tc.wantCborData)
+			}
+		})
+	}
+}
+
+func TestOmitEmptyForBinaryMarshaler1(t *testing.T) {
+	type T1 struct {
+		No number `cbor:"no,omitempty"`
+	}
+	type T struct {
+		Str  T1 `cbor:"str"`
+		Stro T1 `cbor:"stro,omitempty"`
+	}
+
+	testCases := []roundTripTest{
+		{
+			"empty BinaryMarshaler",
+			T1{},
+			[]byte{0xa0}, // {}
+		},
+		{
+			"empty struct containing empty BinaryMarshaler",
+			T{},
+			[]byte{0xa1, 0x63, 0x73, 0x74, 0x72, 0xa0}, // {str: {}}
+		},
+	}
+
+	em, _ := EncOptions{}.EncMode()
+	dm, _ := DecOptions{}.DecMode()
+	testRoundTrip(t, testCases, em, dm)
+}
+
+func TestOmitEmptyForBinaryMarshaler2(t *testing.T) {
+	type T1 struct {
+		So stru `cbor:"so,omitempty"`
+	}
+	type T struct {
+		Str  T1 `cbor:"str"`
+		Stro T1 `cbor:"stro,omitempty"`
+	}
+
+	testCases := []roundTripTest{
+		{
+			"empty BinaryMarshaler",
+			T1{},
+			[]byte{0xa0}, // {}
+		},
+		{
+			"empty struct containing empty BinaryMarshaler",
+			T{},
+			[]byte{0xa1, 0x63, 0x73, 0x74, 0x72, 0xa0}, // {str: {}}
+		},
+	}
+
+	em, _ := EncOptions{}.EncMode()
+	dm, _ := DecOptions{}.DecMode()
+	testRoundTrip(t, testCases, em, dm)
+}
+
+// omitempty is a no-op for time.Time.
+func TestOmitEmptyForTime(t *testing.T) {
+	type T struct {
+		Tm time.Time `cbor:"t,omitempty"`
+	}
+
+	v := T{}
+	want := []byte{0xa1, 0x61, 0x74, 0xf6} // {"t": nil}
+
+	em, _ := EncOptions{}.EncMode()
+	dm, _ := DecOptions{}.DecMode()
+	testRoundTrip(t, []roundTripTest{{"default values", v, want}}, em, dm)
+}
+
+// omitempty is a no-op for big.Int.
+func TestOmitEmptyForBigInt(t *testing.T) {
+	type T struct {
+		I big.Int `cbor:"bi,omitempty"`
+	}
+
+	v := T{}
+	want := []byte{0xa1, 0x62, 0x62, 0x69, 0xc2, 0x40} // {"bi": 2([])}
+
+	em, _ := EncOptions{BigIntConvert: BigIntConvertNone}.EncMode()
+	dm, _ := DecOptions{}.DecMode()
+	testRoundTrip(t, []roundTripTest{{"default values", v, want}}, em, dm)
 }
 
 func TestTaggedField(t *testing.T) {
diff --git a/tag_test.go b/tag_test.go
index e49087d..5117f54 100644
--- a/tag_test.go
+++ b/tag_test.go
@@ -191,6 +191,64 @@ func TestTagBinaryMarshalerUnmarshaler(t *testing.T) {
 }
 
 func TestTagStruct(t *testing.T) {
+	type T struct {
+		S string `cbor:"s,omitempty"`
+	}
+
+	t1 := reflect.TypeOf(T{})
+
+	tags := NewTagSet()
+	if err := tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, t1, 100); err != nil {
+		t.Fatalf("TagSet.Add(%s, %d) returned error %v", t1, 100, err)
+	}
+
+	em, _ := EncOptions{}.EncModeWithTags(tags)
+	dm, _ := DecOptions{}.DecModeWithTags(tags)
+
+	cborData := hexDecode("d864a0") // {}
+	var v T
+	if err := dm.Unmarshal(cborData, &v); err != nil {
+		t.Errorf("Unmarshal() returned error %v", err)
+	}
+	b, err := em.Marshal(v)
+	if err != nil {
+		t.Errorf("Marshal(%+v) returned error %v", v, err)
+	}
+	if !bytes.Equal(b, cborData) {
+		t.Errorf("Marshal(%+v) = 0x%x, want 0x%x", v, b, cborData)
+	}
+}
+
+func TestTagFixedLengthStruct(t *testing.T) {
+	type T struct {
+		S string `cbor:"s"`
+	}
+
+	t1 := reflect.TypeOf(T{})
+
+	tags := NewTagSet()
+	if err := tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, t1, 100); err != nil {
+		t.Fatalf("TagSet.Add(%s, %d) returned error %v", t1, 100, err)
+	}
+
+	em, _ := EncOptions{}.EncModeWithTags(tags)
+	dm, _ := DecOptions{}.DecModeWithTags(tags)
+
+	cborData := hexDecode("d864a1617360") // {"s":""}
+	var v T
+	if err := dm.Unmarshal(cborData, &v); err != nil {
+		t.Errorf("Unmarshal() returned error %v", err)
+	}
+	b, err := em.Marshal(v)
+	if err != nil {
+		t.Errorf("Marshal(%+v) returned error %v", v, err)
+	}
+	if !bytes.Equal(b, cborData) {
+		t.Errorf("Marshal(%+v) = 0x%x, want 0x%x", v, b, cborData)
+	}
+}
+
+func TestTagToArrayStruct(t *testing.T) {
 	type coseHeader struct {
 		Alg int    `cbor:"1,keyasint,omitempty"`
 		Kid []byte `cbor:"4,keyasint,omitempty"`