diff --git a/reflection/get.go b/reflection/get.go
new file mode 100644
index 0000000000000000000000000000000000000000..99349f1dcd809f98948b4b787f7ce870884d4e7f
--- /dev/null
+++ b/reflection/get.go
@@ -0,0 +1,69 @@
+package reflection
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+
+	"gitlab.com/uafrica/go-utils/errors"
+)
+
+func Get(v reflect.Value, key string) (reflect.Value, error) {
+	return get("", v, key)
+}
+
+func get(name string, v reflect.Value, key string) (reflect.Value, error) {
+	if key == "" {
+		return v, nil
+	}
+
+	switch v.Kind() {
+	case reflect.Ptr:
+		return get(name, v.Elem(), key)
+
+	case reflect.Struct:
+		if key[0] != '.' {
+			return v, errors.Errorf("get(%s): key=\"%s\" does not start with '.'", name, key)
+		}
+		fieldName := key[1:]
+		remainingKey := ""
+		index := strings.IndexAny(fieldName, ".[")
+		if index > 0 {
+			fieldName = key[1 : index+1]
+			remainingKey = key[index+1:]
+		}
+		t := v.Type()
+		fieldIndex := 0
+		for fieldIndex = 0; fieldIndex < t.NumField(); fieldIndex++ {
+			if strings.SplitN(t.Field(fieldIndex).Tag.Get("json"), ",", 2)[0] == fieldName {
+				break
+			}
+		}
+		if fieldIndex >= t.NumField() {
+			return v, errors.Errorf("%s does not have field %s", name, fieldName)
+		}
+		return get(name+"."+fieldName, v.Field(fieldIndex), remainingKey)
+
+	case reflect.Slice:
+		if !strings.HasPrefix(key, "[]") {
+			return v, errors.Errorf("canot get %s from slice, expecting \"[]\" in the key", key)
+		}
+
+		//make array of results from each item in the slice
+		var result reflect.Value
+		for i := 0; i < v.Len(); i++ {
+			if vv, err := get(fmt.Sprintf("%s[%d]", name, i), v.Index(i), key[2:]); err != nil {
+				return v, errors.Wrapf(err, "failed on %s[%d]", name, i)
+			} else {
+				if !result.IsValid() {
+					result = reflect.MakeSlice(reflect.SliceOf(vv.Type()), 0, v.Len())
+				}
+				result = reflect.Append(result, vv)
+			}
+		}
+		return result, nil
+
+	default:
+	}
+	return v, errors.Errorf("Cannot get %s from %s", key, v.Kind())
+}
diff --git a/reflection/reflection.go b/reflection/reflection.go
index 14faf28a7641715e420fb3a7fa3a396f117cf0ac..b5ddd2590537f221a130a06fc424cb0ad1d8e2bf 100644
--- a/reflection/reflection.go
+++ b/reflection/reflection.go
@@ -52,7 +52,7 @@ func SetString(field reflect.Value, value string) {
 		return // Field doesn't exist
 	}
 	if field.Kind() != reflect.String {
-		logger.Error("Claims: Field is not of type String: %v", field.Kind())
+		logger.Errorf("Claims: Field is not of type String: %v", field.Kind())
 		return
 	}
 	field.SetString(value)
diff --git a/reflection/type_clone.go b/reflection/type_clone.go
new file mode 100644
index 0000000000000000000000000000000000000000..9cc2d1cbfcb81d787910db54ecb569cd9af0f856
--- /dev/null
+++ b/reflection/type_clone.go
@@ -0,0 +1,68 @@
+package reflection
+
+import (
+	"reflect"
+	"strings"
+
+	"gitlab.com/uafrica/go-utils/errors"
+)
+
+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
+					} 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
+					return replaceType, nil
+				} else {
+					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
+				}
+			} else {
+				return t, nil
+			}
+		}
+
+		return t, errors.Errorf("NYI")
+
+	default:
+	}
+	return t, errors.Errorf("cannot clone %v %s", t.Kind(), t.Name())
+}
diff --git a/reflection/type_clone_test.go b/reflection/type_clone_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..474bc098c500a7aca52106681e6cdcd072af65c0
--- /dev/null
+++ b/reflection/type_clone_test.go
@@ -0,0 +1,134 @@
+package reflection_test
+
+import (
+	"encoding/json"
+	"reflect"
+	"testing"
+
+	"gitlab.com/uafrica/go-utils/reflection"
+)
+
+func Test1(t *testing.T) {
+	doc := `
+		{
+				"took":872,
+				"timed_out":false,
+				"_shards":{
+					"total":38,
+					"successful":38,
+					"skipped":0,
+					"failed":0
+				},
+				"hits":{
+					"total":{
+						"value":10,
+						"relation":"eq"
+					},
+					"max_score":null,
+					"hits":[
+						{
+							"_index": "go-utils-audit-test-20211030",
+							"_type": "_doc",
+							"_id": "Tj9l5XwBWRiAneoYazic",
+							"_score": 1.2039728,
+							"_source": {
+								"@timestamp": "2021-10-30T15:03:20.000000+02:00",
+								"@end_time": "2021-10-30T15:03:21.000000+02:00",
+								"@duration_ms": 1000,
+								"test1": "6",
+								"test2": "ACC_00098",
+								"test3": 10,
+								"http": {
+									"method": "GET",
+									"path": "/accounts"
+								},
+								"http_method": "GET",
+								"http_path": "/accounts"
+							}
+						}
+					]
+				}
+			}
+			`
+
+	//using default type, documents in _source:{} are parsed to map[string]interface{}
+	res := SearchResponseBody{}
+	if err := json.Unmarshal([]byte(doc), &res); err != nil {
+		t.Fatalf("cannot unmarshal response into default type: %+v", err)
+	}
+	for hitIndex, hit := range res.Hits.Hits {
+		t.Logf("doc[%d]: (%T)%+v", hitIndex, hit.Source, hit.Source)
+	}
+
+	//create type with own document type to use for _source field:
+	cloned, err := reflection.CloneType(
+		reflect.TypeOf(SearchResponseBody{}),
+		map[string]reflect.Type{
+			".hits.hits[]._source": reflect.TypeOf(myDoc{}),
+		})
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Logf("cloned type: %v", cloned)
+
+	//unmarshal using new type to have correct type for each hit
+	resPtrValue := reflect.New(cloned)
+	if err := json.Unmarshal([]byte(doc), resPtrValue.Interface()); err != nil {
+		t.Fatalf("failed to decode into cloned type: %+v", err)
+	}
+	// clonedRes := resPtrValue.Interface()
+	// t.Logf("Coned res: %+v", clonedRes)
+
+	//get the replaced values as an array of docs
+	v, err := reflection.Get(resPtrValue, ".hits.hits[]._source")
+	if err != nil {
+		t.Fatalf("Did not get list: %+v", err)
+	}
+	docs, ok := v.Interface().([]myDoc)
+	if !ok {
+		t.Fatalf("%T is not []myDoc", v.Interface())
+	}
+	if len(docs) != 1 {
+		t.Fatalf("Got %d != 1", len(docs))
+	}
+	for _, doc := range docs {
+		t.Logf("doc: (%T)%+v", doc, doc)
+	}
+}
+
+type SearchResponseBody struct {
+	Took     int                  `json:"took"` //milliseconds
+	TimedOut bool                 `json:"timed_out"`
+	Shards   SearchResponseShards `json:"_shards"`
+	Hits     SearchResponseHits   `json:"hits"`
+}
+
+type SearchResponseShards struct {
+	Total      int `json:"total"`
+	Successful int `json:"successful"`
+	Skipped    int `json:"skipped"`
+	Failed     int `json:"failed"`
+}
+
+type SearchResponseHits struct {
+	Total    SearchResponseHitsTotal `json:"total"`
+	MaxScore *float64                `json:"max_score,omitempty"`
+	Hits     []HitDoc                `json:"hits"`
+}
+
+type SearchResponseHitsTotal struct {
+	Value    int    `json:"value"`    //e.g. 0 when no docs matched
+	Relation string `json:"relation"` //e.g. "eq"
+}
+
+type HitDoc struct {
+	Index  string                 `json:"_index"` //name of index
+	Type   string                 `json:"_type"`  //_doc
+	ID     string                 `json:"_id"`
+	Score  float64                `json:"_score"`  //
+	Source map[string]interface{} `json:"_source"` //the document of itemType
+}
+
+type myDoc struct {
+	Test1 string `json:"test1"`
+}