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 }