Skip to content
Snippets Groups Projects
get.go 2.17 KiB
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...
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())
}