Newer
Older
package reflection
import (
"fmt"
"reflect"
"strings"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
// Get() a jq-named element from a value
// e.g. get(reflect.ValueOf(myDoc), ".hits.hits[]._source")
// the result is an array of _source items which may be
// just one field inside the hits object in the list
// see usage in search.TimeSeries.Search() to get documents
// from the OpenSearch response structure that is very nested
// and parse using a runtime-created reflect Type, i.e. one
// cannot get it simply by iterating over res.Hits.[]Hits...
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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())
}