kops/vendor/github.com/oliveagle/jsonpath/jsonpath.go

723 lines
16 KiB
Go

package jsonpath
import (
"errors"
"fmt"
"go/token"
"go/types"
"reflect"
"regexp"
"strconv"
"strings"
)
var ErrGetFromNullObj = errors.New("get attribute from null object")
func JsonPathLookup(obj interface{}, jpath string) (interface{}, error) {
c, err := Compile(jpath)
if err != nil {
return nil, err
}
return c.Lookup(obj)
}
type Compiled struct {
path string
steps []step
}
type step struct {
op string
key string
args interface{}
}
func MustCompile(jpath string) *Compiled {
c, err := Compile(jpath)
if err != nil {
panic(err)
}
return c
}
func Compile(jpath string) (*Compiled, error) {
tokens, err := tokenize(jpath)
if err != nil {
return nil, err
}
if tokens[0] != "@" && tokens[0] != "$" {
return nil, fmt.Errorf("$ or @ should in front of path")
}
tokens = tokens[1:]
res := Compiled{
path: jpath,
steps: make([]step, len(tokens)),
}
for i, token := range tokens {
op, key, args, err := parse_token(token)
if err != nil {
return nil, err
}
res.steps[i] = step{op, key, args}
}
return &res, nil
}
func (c *Compiled) String() string {
return fmt.Sprintf("Compiled lookup: %s", c.path)
}
func (c *Compiled) Lookup(obj interface{}) (interface{}, error) {
var err error
for _, s := range c.steps {
// "key", "idx"
switch s.op {
case "key":
obj, err = get_key(obj, s.key)
if err != nil {
return nil, err
}
case "idx":
if len(s.key) > 0 {
// no key `$[0].test`
obj, err = get_key(obj, s.key)
if err != nil {
return nil, err
}
}
if len(s.args.([]int)) > 1 {
res := []interface{}{}
for _, x := range s.args.([]int) {
//fmt.Println("idx ---- ", x)
tmp, err := get_idx(obj, x)
if err != nil {
return nil, err
}
res = append(res, tmp)
}
obj = res
} else if len(s.args.([]int)) == 1 {
//fmt.Println("idx ----------------3")
obj, err = get_idx(obj, s.args.([]int)[0])
if err != nil {
return nil, err
}
} else {
//fmt.Println("idx ----------------4")
return nil, fmt.Errorf("cannot index on empty slice")
}
case "range":
if len(s.key) > 0 {
// no key `$[:1].test`
obj, err = get_key(obj, s.key)
if err != nil {
return nil, err
}
}
if argsv, ok := s.args.([2]interface{}); ok == true {
obj, err = get_range(obj, argsv[0], argsv[1])
if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("range args length should be 2")
}
case "filter":
obj, err = get_key(obj, s.key)
if err != nil {
return nil, err
}
obj, err = get_filtered(obj, obj, s.args.(string))
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("expression don't support in filter")
}
}
return obj, nil
}
func tokenize(query string) ([]string, error) {
tokens := []string{}
// token_start := false
// token_end := false
token := ""
// fmt.Println("-------------------------------------------------- start")
for idx, x := range query {
token += string(x)
// //fmt.Printf("idx: %d, x: %s, token: %s, tokens: %v\n", idx, string(x), token, tokens)
if idx == 0 {
if token == "$" || token == "@" {
tokens = append(tokens, token[:])
token = ""
continue
} else {
return nil, fmt.Errorf("should start with '$'")
}
}
if token == "." {
continue
} else if token == ".." {
if tokens[len(tokens)-1] != "*" {
tokens = append(tokens, "*")
}
token = "."
continue
} else {
// fmt.Println("else: ", string(x), token)
if strings.Contains(token, "[") {
// fmt.Println(" contains [ ")
if x == ']' && !strings.HasSuffix(token, "\\]") {
if token[0] == '.' {
tokens = append(tokens, token[1:])
} else {
tokens = append(tokens, token[:])
}
token = ""
continue
}
} else {
// fmt.Println(" doesn't contains [ ")
if x == '.' {
if token[0] == '.' {
tokens = append(tokens, token[1:len(token)-1])
} else {
tokens = append(tokens, token[:len(token)-1])
}
token = "."
continue
}
}
}
}
if len(token) > 0 {
if token[0] == '.' {
token = token[1:]
if token != "*" {
tokens = append(tokens, token[:])
} else if tokens[len(tokens)-1] != "*" {
tokens = append(tokens, token[:])
}
} else {
if token != "*" {
tokens = append(tokens, token[:])
} else if tokens[len(tokens)-1] != "*" {
tokens = append(tokens, token[:])
}
}
}
// fmt.Println("finished tokens: ", tokens)
// fmt.Println("================================================= done ")
return tokens, nil
}
/*
op: "root", "key", "idx", "range", "filter", "scan"
*/
func parse_token(token string) (op string, key string, args interface{}, err error) {
if token == "$" {
return "root", "$", nil, nil
}
if token == "*" {
return "scan", "*", nil, nil
}
bracket_idx := strings.Index(token, "[")
if bracket_idx < 0 {
return "key", token, nil, nil
} else {
key = token[:bracket_idx]
tail := token[bracket_idx:]
if len(tail) < 3 {
err = fmt.Errorf("len(tail) should >=3, %v", tail)
return
}
tail = tail[1 : len(tail)-1]
//fmt.Println(key, tail)
if strings.Contains(tail, "?") {
// filter -------------------------------------------------
op = "filter"
if strings.HasPrefix(tail, "?(") && strings.HasSuffix(tail, ")") {
args = strings.Trim(tail[2:len(tail)-1], " ")
}
return
} else if strings.Contains(tail, ":") {
// range ----------------------------------------------
op = "range"
tails := strings.Split(tail, ":")
if len(tails) != 2 {
err = fmt.Errorf("only support one range(from, to): %v", tails)
return
}
var frm interface{}
var to interface{}
if frm, err = strconv.Atoi(strings.Trim(tails[0], " ")); err != nil {
if strings.Trim(tails[0], " ") == "" {
err = nil
}
frm = nil
}
if to, err = strconv.Atoi(strings.Trim(tails[1], " ")); err != nil {
if strings.Trim(tails[1], " ") == "" {
err = nil
}
to = nil
}
args = [2]interface{}{frm, to}
return
} else if tail == "*" {
op = "range"
args = [2]interface{}{nil, nil}
return
} else {
// idx ------------------------------------------------
op = "idx"
res := []int{}
for _, x := range strings.Split(tail, ",") {
if i, err := strconv.Atoi(strings.Trim(x, " ")); err == nil {
res = append(res, i)
} else {
return "", "", nil, err
}
}
args = res
}
}
return op, key, args, nil
}
func filter_get_from_explicit_path(obj interface{}, path string) (interface{}, error) {
steps, err := tokenize(path)
//fmt.Println("f: steps: ", steps, err)
//fmt.Println(path, steps)
if err != nil {
return nil, err
}
if steps[0] != "@" && steps[0] != "$" {
return nil, fmt.Errorf("$ or @ should in front of path")
}
steps = steps[1:]
xobj := obj
//fmt.Println("f: xobj", xobj)
for _, s := range steps {
op, key, args, err := parse_token(s)
// "key", "idx"
switch op {
case "key":
xobj, err = get_key(xobj, key)
if err != nil {
return nil, err
}
case "idx":
if len(args.([]int)) != 1 {
return nil, fmt.Errorf("don't support multiple index in filter")
}
xobj, err = get_key(xobj, key)
if err != nil {
return nil, err
}
xobj, err = get_idx(xobj, args.([]int)[0])
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("expression don't support in filter")
}
}
return xobj, nil
}
func get_key(obj interface{}, key string) (interface{}, error) {
if reflect.TypeOf(obj) == nil {
return nil, ErrGetFromNullObj
}
switch reflect.TypeOf(obj).Kind() {
case reflect.Map:
// if obj came from stdlib json, its highly likely to be a map[string]interface{}
// in which case we can save having to iterate the map keys to work out if the
// key exists
if jsonMap, ok := obj.(map[string]interface{}); ok {
val, exists := jsonMap[key]
if !exists {
return nil, fmt.Errorf("key error: %s not found in object", key)
}
return val, nil
}
for _, kv := range reflect.ValueOf(obj).MapKeys() {
//fmt.Println(kv.String())
if kv.String() == key {
return reflect.ValueOf(obj).MapIndex(kv).Interface(), nil
}
}
return nil, fmt.Errorf("key error: %s not found in object", key)
case reflect.Slice:
// slice we should get from all objects in it.
res := []interface{}{}
for i := 0; i < reflect.ValueOf(obj).Len(); i++ {
tmp, _ := get_idx(obj, i)
if v, err := get_key(tmp, key); err == nil {
res = append(res, v)
}
}
return res, nil
default:
return nil, fmt.Errorf("object is not map")
}
}
func get_idx(obj interface{}, idx int) (interface{}, error) {
switch reflect.TypeOf(obj).Kind() {
case reflect.Slice:
length := reflect.ValueOf(obj).Len()
if idx >= 0 {
if idx >= length {
return nil, fmt.Errorf("index out of range: len: %v, idx: %v", length, idx)
}
return reflect.ValueOf(obj).Index(idx).Interface(), nil
} else {
// < 0
_idx := length + idx
if _idx < 0 {
return nil, fmt.Errorf("index out of range: len: %v, idx: %v", length, idx)
}
return reflect.ValueOf(obj).Index(_idx).Interface(), nil
}
default:
return nil, fmt.Errorf("object is not Slice")
}
}
func get_range(obj, frm, to interface{}) (interface{}, error) {
switch reflect.TypeOf(obj).Kind() {
case reflect.Slice:
length := reflect.ValueOf(obj).Len()
_frm := 0
_to := length
if frm == nil {
frm = 0
}
if to == nil {
to = length - 1
}
if fv, ok := frm.(int); ok == true {
if fv < 0 {
_frm = length + fv
} else {
_frm = fv
}
}
if tv, ok := to.(int); ok == true {
if tv < 0 {
_to = length + tv + 1
} else {
_to = tv + 1
}
}
if _frm < 0 || _frm >= length {
return nil, fmt.Errorf("index [from] out of range: len: %v, from: %v", length, frm)
}
if _to < 0 || _to > length {
return nil, fmt.Errorf("index [to] out of range: len: %v, to: %v", length, to)
}
//fmt.Println("_frm, _to: ", _frm, _to)
res_v := reflect.ValueOf(obj).Slice(_frm, _to)
return res_v.Interface(), nil
default:
return nil, fmt.Errorf("object is not Slice")
}
}
func regFilterCompile(rule string) (*regexp.Regexp, error) {
runes := []rune(rule)
if len(runes) <= 2 {
return nil, errors.New("empty rule")
}
if runes[0] != '/' || runes[len(runes)-1] != '/' {
return nil, errors.New("invalid syntax. should be in `/pattern/` form")
}
runes = runes[1 : len(runes)-1]
return regexp.Compile(string(runes))
}
func get_filtered(obj, root interface{}, filter string) ([]interface{}, error) {
lp, op, rp, err := parse_filter(filter)
if err != nil {
return nil, err
}
res := []interface{}{}
switch reflect.TypeOf(obj).Kind() {
case reflect.Slice:
if op == "=~" {
// regexp
pat, err := regFilterCompile(rp)
if err != nil {
return nil, err
}
for i := 0; i < reflect.ValueOf(obj).Len(); i++ {
tmp := reflect.ValueOf(obj).Index(i).Interface()
ok, err := eval_reg_filter(tmp, root, lp, pat)
if err != nil {
return nil, err
}
if ok == true {
res = append(res, tmp)
}
}
} else {
for i := 0; i < reflect.ValueOf(obj).Len(); i++ {
tmp := reflect.ValueOf(obj).Index(i).Interface()
ok, err := eval_filter(tmp, root, lp, op, rp)
if err != nil {
return nil, err
}
if ok == true {
res = append(res, tmp)
}
}
}
return res, nil
case reflect.Map:
if op == "=~" {
// regexp
pat, err := regFilterCompile(rp)
if err != nil {
return nil, err
}
for _, kv := range reflect.ValueOf(obj).MapKeys() {
tmp := reflect.ValueOf(obj).MapIndex(kv).Interface()
ok, err := eval_reg_filter(tmp, root, lp, pat)
if err != nil {
return nil, err
}
if ok == true {
res = append(res, tmp)
}
}
} else {
for _, kv := range reflect.ValueOf(obj).MapKeys() {
tmp := reflect.ValueOf(obj).MapIndex(kv).Interface()
ok, err := eval_filter(tmp, root, lp, op, rp)
if err != nil {
return nil, err
}
if ok == true {
res = append(res, tmp)
}
}
}
default:
return nil, fmt.Errorf("don't support filter on this type: %v", reflect.TypeOf(obj).Kind())
}
return res, nil
}
// @.isbn => @.isbn, exists, nil
// @.price < 10 => @.price, <, 10
// @.price <= $.expensive => @.price, <=, $.expensive
// @.author =~ /.*REES/i => @.author, match, /.*REES/i
func parse_filter(filter string) (lp string, op string, rp string, err error) {
tmp := ""
stage := 0
str_embrace := false
for idx, c := range filter {
switch c {
case '\'':
if str_embrace == false {
str_embrace = true
} else {
switch stage {
case 0:
lp = tmp
case 1:
op = tmp
case 2:
rp = tmp
}
tmp = ""
}
case ' ':
if str_embrace == true {
tmp += string(c)
continue
}
switch stage {
case 0:
lp = tmp
case 1:
op = tmp
case 2:
rp = tmp
}
tmp = ""
stage += 1
if stage > 2 {
return "", "", "", errors.New(fmt.Sprintf("invalid char at %d: `%c`", idx, c))
}
default:
tmp += string(c)
}
}
if tmp != "" {
switch stage {
case 0:
lp = tmp
op = "exists"
case 1:
op = tmp
case 2:
rp = tmp
}
tmp = ""
}
return lp, op, rp, err
}
func parse_filter_v1(filter string) (lp string, op string, rp string, err error) {
tmp := ""
istoken := false
for _, c := range filter {
if istoken == false && c != ' ' {
istoken = true
}
if istoken == true && c == ' ' {
istoken = false
}
if istoken == true {
tmp += string(c)
}
if istoken == false && tmp != "" {
if lp == "" {
lp = tmp[:]
tmp = ""
} else if op == "" {
op = tmp[:]
tmp = ""
} else if rp == "" {
rp = tmp[:]
tmp = ""
}
}
}
if tmp != "" && lp == "" && op == "" && rp == "" {
lp = tmp[:]
op = "exists"
rp = ""
err = nil
return
} else if tmp != "" && rp == "" {
rp = tmp[:]
tmp = ""
}
return lp, op, rp, err
}
func eval_reg_filter(obj, root interface{}, lp string, pat *regexp.Regexp) (res bool, err error) {
if pat == nil {
return false, errors.New("nil pat")
}
lp_v, err := get_lp_v(obj, root, lp)
if err != nil {
return false, err
}
switch v := lp_v.(type) {
case string:
return pat.MatchString(v), nil
default:
return false, errors.New("only string can match with regular expression")
}
}
func get_lp_v(obj, root interface{}, lp string) (interface{}, error) {
var lp_v interface{}
if strings.HasPrefix(lp, "@.") {
return filter_get_from_explicit_path(obj, lp)
} else if strings.HasPrefix(lp, "$.") {
return filter_get_from_explicit_path(root, lp)
} else {
lp_v = lp
}
return lp_v, nil
}
func eval_filter(obj, root interface{}, lp, op, rp string) (res bool, err error) {
lp_v, err := get_lp_v(obj, root, lp)
if op == "exists" {
return lp_v != nil, nil
} else if op == "=~" {
return false, fmt.Errorf("not implemented yet")
} else {
var rp_v interface{}
if strings.HasPrefix(rp, "@.") {
rp_v, err = filter_get_from_explicit_path(obj, rp)
} else if strings.HasPrefix(rp, "$.") {
rp_v, err = filter_get_from_explicit_path(root, rp)
} else {
rp_v = rp
}
//fmt.Printf("lp_v: %v, rp_v: %v\n", lp_v, rp_v)
return cmp_any(lp_v, rp_v, op)
}
}
func isNumber(o interface{}) bool {
switch v := o.(type) {
case int, int8, int16, int32, int64:
return true
case uint, uint8, uint16, uint32, uint64:
return true
case float32, float64:
return true
case string:
_, err := strconv.ParseFloat(v, 64)
if err == nil {
return true
} else {
return false
}
}
return false
}
func cmp_any(obj1, obj2 interface{}, op string) (bool, error) {
switch op {
case "<", "<=", "==", ">=", ">":
default:
return false, fmt.Errorf("op should only be <, <=, ==, >= and >")
}
var exp string
if isNumber(obj1) && isNumber(obj2) {
exp = fmt.Sprintf(`%v %s %v`, obj1, op, obj2)
} else {
exp = fmt.Sprintf(`"%v" %s "%v"`, obj1, op, obj2)
}
//fmt.Println("exp: ", exp)
fset := token.NewFileSet()
res, err := types.Eval(fset, nil, 0, exp)
if err != nil {
return false, err
}
if res.IsValue() == false || (res.Value.String() != "false" && res.Value.String() != "true") {
return false, fmt.Errorf("result should only be true or false")
}
if res.Value.String() == "true" {
return true, nil
}
return false, nil
}