Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 31 additions & 14 deletions cbor/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"bytes"
"fmt"
"reflect"
"sync"

_cbor "github.com/fxamacker/cbor/v2"
"github.com/jinzhu/copier"
Expand Down Expand Up @@ -109,27 +110,43 @@ func DecodeById(
return ret, nil
}

var decodeGenericTypeCache = map[reflect.Type]reflect.Type{}
var decodeGenericTypeCacheMutex sync.RWMutex

// DecodeGeneric decodes the specified CBOR into the destination object without using the
// destination object's UnmarshalCBOR() function
func DecodeGeneric(cborData []byte, dest interface{}) error {
// Create a duplicate(-ish) struct from the destination
// We do this so that we can bypass any custom UnmarshalCBOR() function on the
// destination object
// Get destination type
valueDest := reflect.ValueOf(dest)
if valueDest.Kind() != reflect.Pointer ||
valueDest.Elem().Kind() != reflect.Struct {
return fmt.Errorf("destination must be a pointer to a struct")
}
typeDestElem := valueDest.Elem().Type()
destTypeFields := []reflect.StructField{}
for i := 0; i < typeDestElem.NumField(); i++ {
tmpField := typeDestElem.Field(i)
if tmpField.IsExported() && tmpField.Name != "DecodeStoreCbor" {
destTypeFields = append(destTypeFields, tmpField)
typeDest := valueDest.Elem().Type()
// Check type cache
decodeGenericTypeCacheMutex.RLock()
tmpTypeDest, ok := decodeGenericTypeCache[typeDest]
decodeGenericTypeCacheMutex.RUnlock()
if !ok {
// Create a duplicate(-ish) struct from the destination
// We do this so that we can bypass any custom UnmarshalCBOR() function on the
// destination object
if valueDest.Kind() != reflect.Pointer ||
valueDest.Elem().Kind() != reflect.Struct {
decodeGenericTypeCacheMutex.Unlock()
return fmt.Errorf("destination must be a pointer to a struct")
}
destTypeFields := []reflect.StructField{}
for i := 0; i < typeDest.NumField(); i++ {
tmpField := typeDest.Field(i)
if tmpField.IsExported() && tmpField.Name != "DecodeStoreCbor" {
destTypeFields = append(destTypeFields, tmpField)
}
}
tmpTypeDest = reflect.StructOf(destTypeFields)
// Populate cache
decodeGenericTypeCacheMutex.Lock()
decodeGenericTypeCache[typeDest] = tmpTypeDest
decodeGenericTypeCacheMutex.Unlock()
}
// Create temporary object with the type created above
tmpDest := reflect.New(reflect.StructOf(destTypeFields))
tmpDest := reflect.New(tmpTypeDest)
// Decode CBOR into temporary object
if _, err := Decode(cborData, tmpDest.Interface()); err != nil {
return err
Expand Down
44 changes: 30 additions & 14 deletions cbor/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"bytes"
"fmt"
"reflect"
"sync"

_cbor "github.com/fxamacker/cbor/v2"
"github.com/jinzhu/copier"
Expand All @@ -38,27 +39,42 @@ func Encode(data interface{}) ([]byte, error) {
return buf.Bytes(), err
}

var encodeGenericTypeCache = map[reflect.Type]reflect.Type{}
var encodeGenericTypeCacheMutex sync.RWMutex

// EncodeGeneric encodes the specified object to CBOR without using the source object's
// MarshalCBOR() function
func EncodeGeneric(src interface{}) ([]byte, error) {
// Create a duplicate(-ish) struct from the destination
// We do this so that we can bypass any custom UnmarshalCBOR() function on the
// destination object
// Get source type
valueSrc := reflect.ValueOf(src)
if valueSrc.Kind() != reflect.Pointer ||
valueSrc.Elem().Kind() != reflect.Struct {
return nil, fmt.Errorf("source must be a pointer to a struct")
}
typeSrcElem := valueSrc.Elem().Type()
srcTypeFields := []reflect.StructField{}
for i := 0; i < typeSrcElem.NumField(); i++ {
tmpField := typeSrcElem.Field(i)
if tmpField.IsExported() && tmpField.Name != "DecodeStoreCbor" {
srcTypeFields = append(srcTypeFields, tmpField)
typeSrc := valueSrc.Elem().Type()
// Check type cache
encodeGenericTypeCacheMutex.RLock()
tmpTypeSrc, ok := encodeGenericTypeCache[typeSrc]
encodeGenericTypeCacheMutex.RUnlock()
if !ok {
// Create a duplicate(-ish) struct from the destination
// We do this so that we can bypass any custom MarshalCBOR() function on the
// source object
if valueSrc.Kind() != reflect.Pointer ||
valueSrc.Elem().Kind() != reflect.Struct {
return nil, fmt.Errorf("source must be a pointer to a struct")
}
srcTypeFields := []reflect.StructField{}
for i := 0; i < typeSrc.NumField(); i++ {
tmpField := typeSrc.Field(i)
if tmpField.IsExported() && tmpField.Name != "DecodeStoreCbor" {
srcTypeFields = append(srcTypeFields, tmpField)
}
}
tmpTypeSrc = reflect.StructOf(srcTypeFields)
// Populate cache
encodeGenericTypeCacheMutex.Lock()
encodeGenericTypeCache[typeSrc] = tmpTypeSrc
encodeGenericTypeCacheMutex.Unlock()
}
// Create temporary object with the type created above
tmpSrc := reflect.New(reflect.StructOf(srcTypeFields))
tmpSrc := reflect.New(tmpTypeSrc)
// Copy values from source object into temporary object
if err := copier.Copy(tmpSrc.Interface(), src); err != nil {
return nil, err
Expand Down