Skip to content
Snippets Groups Projects
Select Git revision
  • a658b96ace8cd633e947ae7436796a75dad325fb
  • main default protected
  • 1-mage-run-does-not-stop-containers
  • v0.26.0
  • v0.25.0
  • v0.24.0
  • v0.23.0
  • v0.22.0
  • v0.21.0
  • v0.20.0
  • v0.19.0
  • v0.18.0
  • v0.17.0
  • v0.16.0
  • v0.15.0
  • v0.14.0
  • v0.13.0
  • v0.12.0
  • v0.11.0
  • v0.10.0
  • v0.9.0
  • v0.8.0
  • v0.7.0
23 results

file_system.go

Blame
  • 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())
    }