Repository: fxamacker/cbor (go)

Issue:
feature: support deserializing registered CBOR tag to an interface type 
### What version of fxamacker/cbor are you using?
`2.3.0`

### Does this issue reproduce with the latest release?


### What OS and CPU architecture are you using (`go env`)?

<details><summary><code>go env</code> Output</summary><br><pre>
$ go env
set GO111MODULE=on
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\Guillaume\AppData\Local\go-build
set GOENV=C:\Users\Guillaume\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\Guillaume\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\Guillaume\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\apps\Go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=C:\apps\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.17.3
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=D:\Coding\Go\test-cbor\go.mod
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\GUILLA~1\AppData\Local\Temp\go-build758706208=/tmp/go-build -gno-record-gc c-switches
</pre></details>

### What did you do?

This code prints an err at the Unmarshal step.
```go
type B interface {
	Foo()
}

type C struct {
	Field int
}

func (c C) Foo() {

}

type A struct {
	Field B
}

func main() {
	tags := cbor.NewTagSet()
	err := tags.Add(cbor.TagOptions{EncTag: cbor.EncTagRequired, DecTag: cbor.DecTagRequired}, reflect.TypeOf(C{}), 279)
	if err != nil {
		panic(err)
	}
	encMode, _ := cbor.PreferredUnsortedEncOptions().EncModeWithTags(tags)
	decMode, _ := cbor.DecOptions{}.DecModeWithTags(tags)

	data, _ := encMode.Marshal(A{Field: C{Field: 5}})
	var orig A
	err = decMode.Unmarshal(data, &orig)
	if err != nil {
		panic(err)
	}
	fmt.Println(orig)
}
```

I either do not understand how to use this library or there is a bug somewhere.


### What did you expect to see?

Not a panic. Unmarshal should be working.

### What did you see instead?

```
panic: cbor: cannot unmarshal map into Go struct field main.A.Field of type main.B
```


If I define the interface B like that instead : 
```go
type B interface {}
```
B becomes an alias to interface{} and then Unmarshal works, correctly reading the cbor tag.


Pull request:
Add support for decoding registered CBOR tag to interface type
### Description

Closes #301 

Modify decoder to create new value of registered type and decode data to that value when unmarshaling CBOR data with registered tag to interface type.


Patch:
diff --git a/README.md b/README.md
index 8ae9b4d..aa4b31d 100644
--- a/README.md
+++ b/README.md
@@ -373,6 +373,7 @@ See [Options](#options) section for details about each setting.
 * Decoder always checks for invalid UTF-8 string errors.
 * Decoder always decodes in-place to slices, maps, and structs.
 * Decoder tries case-sensitive first and falls back to case-insensitive field name match when decoding to structs. 
+* Decoder supports decoding registered CBOR tag data to interface types. 
 * Both encoder and decoder support indefinite length CBOR data (["streaming"](https://tools.ietf.org/html/rfc7049#section-2.2)).
 * Both encoder and decoder correctly handles nil slice, map, pointer, and interface values.
 
@@ -454,6 +455,7 @@ If any of these limitations prevent you from using this library, please open an
 * CBOR `Undefined` (0xf7) value decodes to Go's `nil` value.  CBOR `Null` (0xf6) more closely matches Go's `nil`.
 * CBOR map keys with data types not supported by Go for map keys are ignored and an error is returned after continuing to decode remaining items.  
 * When using io.Reader interface to read very large or indefinite length CBOR data, Go's `io.LimitReader` should be used to limit size.
+* When decoding registered CBOR tag data to interface type, decoder creates a pointer to registered Go type matching CBOR tag number.  Requiring a pointer for this is a Go limitation. 
 
 <hr>
 
diff --git a/decode.go b/decode.go
index da67229..0d5f253 100644
--- a/decode.go
+++ b/decode.go
@@ -553,9 +553,34 @@ const (
 // and does not perform bounds checking.
 func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolint:gocyclo
 
-	if tInfo.spclType == specialTypeIface && !v.IsNil() {
-		v = v.Elem()
-		tInfo = getTypeInfo(v.Type())
+	if tInfo.spclType == specialTypeIface {
+		if !v.IsNil() {
+			// Use value type
+			v = v.Elem()
+			tInfo = getTypeInfo(v.Type())
+		} else {
+			// Create and use registered type if CBOR data is registered tag
+			if d.dm.tags != nil && d.nextCBORType() == cborTypeTag {
+
+				off := d.off
+				var tagNums []uint64
+				for d.nextCBORType() == cborTypeTag {
+					_, _, tagNum := d.getHead()
+					tagNums = append(tagNums, tagNum)
+				}
+				d.off = off
+
+				registeredType := d.dm.tags.getTypeFromTagNum(tagNums)
+				if registeredType != nil {
+					if registeredType.Implements(tInfo.nonPtrType) ||
+						reflect.PtrTo(registeredType).Implements(tInfo.nonPtrType) {
+						v.Set(reflect.New(registeredType))
+						v = v.Elem()
+						tInfo = getTypeInfo(registeredType)
+					}
+				}
+			}
+		}
 	}
 
 	// Create new value for the pointer v to point to if CBOR value is not nil/undefined.


Test patch:
diff --git a/decode_test.go b/decode_test.go
index 40e6e48..1eee4bb 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -5401,6 +5401,96 @@ func TestUnmarshalTaggedDataToInterface(t *testing.T) {
 	}
 }
 
+type B interface {
+	Foo()
+}
+
+type C struct {
+	Field int
+}
+
+func (c *C) Foo() {}
+
+type D struct {
+	Field string
+}
+
+func (d *D) Foo() {}
+
+type A1 struct {
+	Field B
+}
+
+type A2 struct {
+	Fields []B
+}
+
+func TestUnmarshalRegisteredTagToInterface(t *testing.T) {
+	var err error
+	tags := NewTagSet()
+	err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(C{}), 279)
+	if err != nil {
+		t.Error(err)
+	}
+	err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(D{}), 280)
+	if err != nil {
+		t.Error(err)
+	}
+
+	encMode, _ := PreferredUnsortedEncOptions().EncModeWithTags(tags)
+	decMode, _ := DecOptions{}.DecModeWithTags(tags)
+
+	v1 := A1{Field: &C{Field: 5}}
+	data1, err := encMode.Marshal(v1)
+	if err != nil {
+		t.Fatalf("Marshal(%+v) returned error %v", v1, err)
+	}
+
+	v2 := A2{Fields: []B{&C{Field: 5}, &D{Field: "a"}}}
+	data2, err := encMode.Marshal(v2)
+	if err != nil {
+		t.Fatalf("Marshal(%+v) returned error %v", v2, err)
+	}
+
+	testCases := []struct {
+		name           string
+		data           []byte
+		unmarshalToObj interface{}
+		wantValue      interface{}
+	}{
+		{
+			name:           "interface type",
+			data:           data1,
+			unmarshalToObj: &A1{},
+			wantValue:      &v1,
+		},
+		{
+			name:           "concrete type",
+			data:           data1,
+			unmarshalToObj: &A1{Field: &C{}},
+			wantValue:      &v1,
+		},
+		{
+			name:           "slice of interface type",
+			data:           data2,
+			unmarshalToObj: &A2{},
+			wantValue:      &v2,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err = decMode.Unmarshal(tc.data, tc.unmarshalToObj)
+			if err != nil {
+				t.Errorf("Unmarshal(0x%x) returned error %v", tc.data, err)
+			}
+			if !reflect.DeepEqual(tc.unmarshalToObj, tc.wantValue) {
+				t.Errorf("Unmarshal(0x%x) = %v, want %v", tc.data, tc.unmarshalToObj, tc.wantValue)
+			}
+		})
+	}
+}
+
 func TestDecModeInvalidDefaultMapType(t *testing.T) {
 	testCases := []struct {
 		name         string