Skip to content

Commit

Permalink
Merge pull request #308 from fxamacker/fxamacker/decode-registered-cb…
Browse files Browse the repository at this point in the history
…or-tag-to-registered-type

Add support for decoding registered CBOR tag to interface type
  • Loading branch information
fxamacker authored Dec 29, 2021
2 parents 466959a + 4d014d2 commit 5ea68a6
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 3 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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>

Expand Down
31 changes: 28 additions & 3 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
90 changes: 90 additions & 0 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 5ea68a6

Please sign in to comment.