Skip to content

Commit 5ea68a6

Browse files
authored
Merge pull request #308 from fxamacker/fxamacker/decode-registered-cbor-tag-to-registered-type
Add support for decoding registered CBOR tag to interface type
2 parents 466959a + 4d014d2 commit 5ea68a6

File tree

3 files changed

+120
-3
lines changed

3 files changed

+120
-3
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ See [Options](#options) section for details about each setting.
373373
* Decoder always checks for invalid UTF-8 string errors.
374374
* Decoder always decodes in-place to slices, maps, and structs.
375375
* Decoder tries case-sensitive first and falls back to case-insensitive field name match when decoding to structs.
376+
* Decoder supports decoding registered CBOR tag data to interface types.
376377
* Both encoder and decoder support indefinite length CBOR data (["streaming"](https://tools.ietf.org/html/rfc7049#section-2.2)).
377378
* Both encoder and decoder correctly handles nil slice, map, pointer, and interface values.
378379

@@ -454,6 +455,7 @@ If any of these limitations prevent you from using this library, please open an
454455
* CBOR `Undefined` (0xf7) value decodes to Go's `nil` value. CBOR `Null` (0xf6) more closely matches Go's `nil`.
455456
* 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.
456457
* When using io.Reader interface to read very large or indefinite length CBOR data, Go's `io.LimitReader` should be used to limit size.
458+
* 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.
457459

458460
<hr>
459461

decode.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -553,9 +553,34 @@ const (
553553
// and does not perform bounds checking.
554554
func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolint:gocyclo
555555

556-
if tInfo.spclType == specialTypeIface && !v.IsNil() {
557-
v = v.Elem()
558-
tInfo = getTypeInfo(v.Type())
556+
if tInfo.spclType == specialTypeIface {
557+
if !v.IsNil() {
558+
// Use value type
559+
v = v.Elem()
560+
tInfo = getTypeInfo(v.Type())
561+
} else {
562+
// Create and use registered type if CBOR data is registered tag
563+
if d.dm.tags != nil && d.nextCBORType() == cborTypeTag {
564+
565+
off := d.off
566+
var tagNums []uint64
567+
for d.nextCBORType() == cborTypeTag {
568+
_, _, tagNum := d.getHead()
569+
tagNums = append(tagNums, tagNum)
570+
}
571+
d.off = off
572+
573+
registeredType := d.dm.tags.getTypeFromTagNum(tagNums)
574+
if registeredType != nil {
575+
if registeredType.Implements(tInfo.nonPtrType) ||
576+
reflect.PtrTo(registeredType).Implements(tInfo.nonPtrType) {
577+
v.Set(reflect.New(registeredType))
578+
v = v.Elem()
579+
tInfo = getTypeInfo(registeredType)
580+
}
581+
}
582+
}
583+
}
559584
}
560585

561586
// Create new value for the pointer v to point to if CBOR value is not nil/undefined.

decode_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5401,6 +5401,96 @@ func TestUnmarshalTaggedDataToInterface(t *testing.T) {
54015401
}
54025402
}
54035403

5404+
type B interface {
5405+
Foo()
5406+
}
5407+
5408+
type C struct {
5409+
Field int
5410+
}
5411+
5412+
func (c *C) Foo() {}
5413+
5414+
type D struct {
5415+
Field string
5416+
}
5417+
5418+
func (d *D) Foo() {}
5419+
5420+
type A1 struct {
5421+
Field B
5422+
}
5423+
5424+
type A2 struct {
5425+
Fields []B
5426+
}
5427+
5428+
func TestUnmarshalRegisteredTagToInterface(t *testing.T) {
5429+
var err error
5430+
tags := NewTagSet()
5431+
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(C{}), 279)
5432+
if err != nil {
5433+
t.Error(err)
5434+
}
5435+
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(D{}), 280)
5436+
if err != nil {
5437+
t.Error(err)
5438+
}
5439+
5440+
encMode, _ := PreferredUnsortedEncOptions().EncModeWithTags(tags)
5441+
decMode, _ := DecOptions{}.DecModeWithTags(tags)
5442+
5443+
v1 := A1{Field: &C{Field: 5}}
5444+
data1, err := encMode.Marshal(v1)
5445+
if err != nil {
5446+
t.Fatalf("Marshal(%+v) returned error %v", v1, err)
5447+
}
5448+
5449+
v2 := A2{Fields: []B{&C{Field: 5}, &D{Field: "a"}}}
5450+
data2, err := encMode.Marshal(v2)
5451+
if err != nil {
5452+
t.Fatalf("Marshal(%+v) returned error %v", v2, err)
5453+
}
5454+
5455+
testCases := []struct {
5456+
name string
5457+
data []byte
5458+
unmarshalToObj interface{}
5459+
wantValue interface{}
5460+
}{
5461+
{
5462+
name: "interface type",
5463+
data: data1,
5464+
unmarshalToObj: &A1{},
5465+
wantValue: &v1,
5466+
},
5467+
{
5468+
name: "concrete type",
5469+
data: data1,
5470+
unmarshalToObj: &A1{Field: &C{}},
5471+
wantValue: &v1,
5472+
},
5473+
{
5474+
name: "slice of interface type",
5475+
data: data2,
5476+
unmarshalToObj: &A2{},
5477+
wantValue: &v2,
5478+
},
5479+
}
5480+
5481+
for _, tc := range testCases {
5482+
t.Run(tc.name, func(t *testing.T) {
5483+
err = decMode.Unmarshal(tc.data, tc.unmarshalToObj)
5484+
if err != nil {
5485+
t.Errorf("Unmarshal(0x%x) returned error %v", tc.data, err)
5486+
}
5487+
if !reflect.DeepEqual(tc.unmarshalToObj, tc.wantValue) {
5488+
t.Errorf("Unmarshal(0x%x) = %v, want %v", tc.data, tc.unmarshalToObj, tc.wantValue)
5489+
}
5490+
})
5491+
}
5492+
}
5493+
54045494
func TestDecModeInvalidDefaultMapType(t *testing.T) {
54055495
testCases := []struct {
54065496
name string

0 commit comments

Comments
 (0)