Select Git revision
file_system.go
type_clone.go 3.33 KiB
package reflection
import (
"reflect"
"strings"
"gitlab.com/uafrica/go-utils/errors"
)
//CloneType() clones a type into a new type, replacing some elements of it
//Paramters:
// t is the existing type to be cloned
// replace is a list of items to replace with their new types
// use jq key notation
// if this is empty map/nil, it will clone the type without changes
//Return:
// cloned type or error
//
//Example:
// newType,err := reflection.CloneType(
// reflect.TypeOf(myStruct{}),
// map[string]reflect.Type{
// ".field1": reflect.TypeOf(float64(0)),
// ".list[].name": reflect.TypeOf(int64(0)),
// },
// )
//
//This is not a function you will use everyday, but very useful when needed
//See example usage in search to read OpenSearch responses with the correct
//struct type used to parse ".hits.hits[]._source" so that we do not have to
//unmarshal the JSON, then marshal each _source from map[string]interface{}
//back to json then unmarshal again into the correct type!
//It saves a lot of overhead CPU doing it all at once using the correct type
//nested deep into the response body type.
//
//this function was written for above case... it will likely need extension
//if we want to replace all kinds of other fields, but it meets the current
//requirements.
//
//After parsing, use reflection.Get() with the same key notation to get the
//result from the nested document.
//
//Note current shortcoming: partial matching will apply if you have two
//similarly named fields, e.g. "name" and "name2", then replace instruction
//on "name" may partial match name2. To fix this, we need better function
//than strings.HasPrefix() to check for delimiter/end of name to do full
//word matches, and we need to extend the test to illustrate this.
func CloneType(t reflect.Type, replace map[string]reflect.Type) (reflect.Type, error) {
return clone("", t, replace)
}
func clone(name string, t reflect.Type, replace map[string]reflect.Type) (reflect.Type, error) {
switch t.Kind() {
case reflect.Struct:
fields := []reflect.StructField{}
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
n := strings.SplitN(f.Tag.Get("json"), ",", 2)[0] //exclude ,omitempty...
if n == "" {
n = f.Name
}
fieldName := name + "." + n
for replaceName, replaceType := range replace {
if strings.HasPrefix(replaceName, fieldName) {
if replaceName == fieldName {
f.Type = replaceType
delete(replace, replaceName)
} else {
clonedType, err := clone(fieldName, f.Type, replace)
if err != nil {
return t, errors.Wrapf(err, "failed to clone %s", fieldName)
}
f.Type = clonedType
}
}
}
if newType, ok := replace[fieldName]; ok {
f.Type = newType
}
fields = append(fields, f)
}
return reflect.StructOf(fields), nil
case reflect.Slice:
for replaceName, replaceType := range replace {
if strings.HasPrefix(replaceName, name+"[]") {
if replaceName == name+"[]" {
//full match
delete(replace, replaceName)
return replaceType, nil
}
//partial match
elemType, err := clone(name+"[]", t.Elem(), replace)
if err != nil {
return t, errors.Wrapf(err, "failed to clone slice elem type")
}
return reflect.SliceOf(elemType), nil
}
}
//no match
return t, nil
default:
}
return t, errors.Errorf("cannot clone %v %s", t.Kind(), t.Name())
}