diff --git a/cbor/decode.go b/cbor/decode.go index ec30f8c1..f967ceb6 100644 --- a/cbor/decode.go +++ b/cbor/decode.go @@ -18,6 +18,7 @@ import ( "bytes" "fmt" "reflect" + "sync" _cbor "github.com/fxamacker/cbor/v2" "github.com/jinzhu/copier" @@ -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 diff --git a/cbor/encode.go b/cbor/encode.go index 442b4be6..74484f96 100644 --- a/cbor/encode.go +++ b/cbor/encode.go @@ -18,6 +18,7 @@ import ( "bytes" "fmt" "reflect" + "sync" _cbor "github.com/fxamacker/cbor/v2" "github.com/jinzhu/copier" @@ -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