// Package ask provides a simple way of accessing nested properties in maps and arrays. // Works great in combination with encoding/json and other packages that "Unmarshal" arbitrary data into Go data-types. // Inspired by the get function in the lodash javascript library. package ask import ( "math" "reflect" "regexp" "strconv" "strings" ) var tokenMatcher = regexp.MustCompile(`([^[]+)?(?:\[(\d+)])?`) var mapType = reflect.TypeOf(map[string]interface{}{}) var sliceType = reflect.TypeOf([]interface{}{}) // Answer holds result of call to For, use one of its methods to extract a value. type Answer struct { value interface{} } func handleIntPart(current interface{}, part int) (interface{}, bool) { val := reflect.ValueOf(current) if val.IsValid() && val.CanConvert(sliceType) { s := val.Convert(sliceType).Interface().([]interface{}) if part >= 0 && part < len(s) { return s[part], false } } return current, true } func handleStringPart(current interface{}, part string) (interface{}, bool) { notFound := false match := tokenMatcher.FindStringSubmatch(strings.TrimSpace(part)) if len(match) == 3 { if match[1] != "" { val := reflect.ValueOf(current) if val.IsValid() && val.CanConvert(mapType) { current = val.Convert(mapType).Interface().(map[string]interface{})[match[1]] } else { notFound = true } } if match[2] != "" { index, _ := strconv.Atoi(match[2]) return handleIntPart(current, index) } } return current, notFound } // For is used to select a path from source to return as answer. func For(source interface{}, path string) *Answer { parts := strings.Split(path, ".") notFound := false current := source for _, part := range parts { current, notFound = handleStringPart(current, part) if notFound { return &Answer{} } } return &Answer{value: current} } // ForArgs is used to select a path using individual arguments from source to return as answer. func ForArgs(source interface{}, parts ...interface{}) *Answer { current := source notFound := false for _, part := range parts { switch vt := part.(type) { case uint, uint8, uint16, uint32, uint64, int, int8, int16, int32, int64: index := reflect.ValueOf(vt).Int() current, notFound = handleIntPart(current, int(index)) if notFound { return &Answer{} } case string: current, notFound = handleStringPart(current, vt) if notFound { return &Answer{} } } } return &Answer{value: current} } // Path does the same thing as For but uses existing answer as source. func (a *Answer) Path(path string) *Answer { return For(a.value, path) } // PathArgs does the same thing as ForArgs but uses existing answer as source. func (a *Answer) PathArgs(parts ...interface{}) *Answer { return ForArgs(a.value, parts...) } // Exists returns a boolean indicating if the answer exists (not nil). func (a *Answer) Exists() bool { return a.value != nil } // Value returns the raw value as type interface{}, can be nil if no value is available. func (a *Answer) Value() interface{} { return a.value } // Slice attempts asserting answer as a []interface{}. // The first return value is the result, and the second indicates if the operation was successful. // If not successful the first return value will be set to the d parameter. func (a *Answer) Slice(d []interface{}) ([]interface{}, bool) { val := reflect.ValueOf(a.value) if val.IsValid() && val.CanConvert(sliceType) { return val.Convert(sliceType).Interface().([]interface{}), true } return d, false } // Map attempts asserting answer as a map[string]interface{}. // The first return value is the result, and the second indicates if the operation was successful. // If not successful the first return value will be set to the d parameter. func (a *Answer) Map(d map[string]interface{}) (map[string]interface{}, bool) { val := reflect.ValueOf(a.value) if val.IsValid() && val.CanConvert(mapType) { return val.Convert(mapType).Interface().(map[string]interface{}), true } return d, false } // String attempts asserting answer as a string. // The first return value is the result, and the second indicates if the operation was successful. // If not successful the first return value will be set to the d parameter. func (a *Answer) String(d string) (string, bool) { res, ok := a.value.(string) if ok { return res, ok } return d, false } // Bool attempts asserting answer as a bool. // The first return value is the result, and the second indicates if the operation was successful. // If not successful the first return value will be set to the d parameter. func (a *Answer) Bool(d bool) (bool, bool) { res, ok := a.value.(bool) if ok { return res, ok } return d, false } // Int attempts asserting answer as a int64. Casting from other number types will be done if necessary. // The first return value is the result, and the second indicates if the operation was successful. // If not successful the first return value will be set to the d parameter. func (a *Answer) Int(d int64) (int64, bool) { switch vt := a.value.(type) { case int, int8, int16, int32, int64: return reflect.ValueOf(vt).Int(), true case uint, uint8, uint16, uint32, uint64: val := reflect.ValueOf(vt).Uint() if val <= math.MaxInt64 { return int64(val), true } case float32, float64: val := reflect.ValueOf(vt).Float() if val >= math.MinInt64 && val <= math.MaxInt64 { return int64(val), true } } return d, false } // Uint attempts asserting answer as a uint64. Casting from other number types will be done if necessary. // The first return value is the result, and the second indicates if the operation was successful. // If not successful the first return value will be set to the d parameter. func (a *Answer) Uint(d uint64) (uint64, bool) { switch vt := a.value.(type) { case int, int8, int16, int32, int64: val := reflect.ValueOf(vt).Int() if val >= 0 { return uint64(val), true } case uint, uint8, uint16, uint32, uint64: return reflect.ValueOf(vt).Uint(), true case float32, float64: val := reflect.ValueOf(vt).Float() if val >= 0 && val <= math.MaxUint64 { return uint64(val), true } } return d, false } // Float attempts asserting answer as a float64. Casting from other number types will be done if necessary. // The first return value is the result, and the second indicates if the operation was successful. // If not successful the first return value will be set to the d parameter. func (a *Answer) Float(d float64) (float64, bool) { switch vt := a.value.(type) { case int, int8, int16, int32, int64: return float64(reflect.ValueOf(vt).Int()), true case uint, uint8, uint16, uint32, uint64: return float64(reflect.ValueOf(vt).Uint()), true case float32: return float64(vt), true case float64: return vt, true } return d, false }