4 A port of the Perl, and JavaScript versions of JSONPath
5 see http://goessner.net/articles/JsonPath/
7 Based on on JavaScript version by Stefan Goessner at:
8 http://code.google.com/p/jsonpath/
9 and Perl version by Kate Rhodes at:
10 http://github.com/masukomi/jsonpath-perl/tree/master
16 __author__ = "Phil Budne"
17 __revision__ = "$Revision: 1.13 $"
20 # Copyright (c) 2007 Stefan Goessner (goessner.net)
21 # Copyright (c) 2008 Kate Rhodes (masukomi.org)
22 # Copyright (c) 2008-2012 Philip Budne (ultimate.com)
23 # Licensed under the MIT licence:
25 # Permission is hereby granted, free of charge, to any person
26 # obtaining a copy of this software and associated documentation
27 # files (the "Software"), to deal in the Software without
28 # restriction, including without limitation the rights to use,
29 # copy, modify, merge, publish, distribute, sublicense, and/or sell
30 # copies of the Software, and to permit persons to whom the
31 # Software is furnished to do so, subject to the following
34 # The above copyright notice and this permission notice shall be
35 # included in all copies or substantial portions of the Software.
37 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
38 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
39 # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
40 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
41 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
42 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
43 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
44 # OTHER DEALINGS IN THE SOFTWARE.
47 # evalx is generally a crock:
48 # handle !@.name.name???
49 # there are probably myriad unexpected ways to get an exception:
50 # wrap initial "trace" call in jsonpath body in a try/except??
53 # internally keep paths as lists to preserve integer types
54 # (instead of as ';' delimited strings)
56 __all__ = ['jsonpath']
59 # XXX precompile RE objects on load???
60 # re_1 = re.compile(.....)
61 # re_2 = re.compile(.....)
64 """normalize the path expression; outside jsonpath to allow testing"""
67 # replace index/filter expressions with placeholders
68 # Python anonymous functions (lambdas) are cryptic, hard to debug
70 n = len(subx) # before append
76 x = re.sub(r"[\['](\??\(.*?\))[\]']", f1, x)
78 # added the negative lookbehind -krhodes
79 x = re.sub(r"'?(?<!@)\.'?|\['?", ";", x)
81 x = re.sub(r";;;|;;", ";..;", x)
83 x = re.sub(r";$|'?\]|'$", "", x)
85 # put expressions back
90 x = re.sub(r"#([0-9]+)", f2, x)
95 def jsonpath(obj, expr, result_type='VALUE', debug=0, use_eval=True):
96 """traverse JSON object using jsonpath expr, returning values or paths"""
99 """concatenate path elements"""
100 return str(x) + ';' + str(y)
103 """check if argument represents a decimal integer"""
107 """convert internal path representation to
108 "full bracket notation" for PATH output"""
110 for piece in path.split(';')[1:]:
111 # make a guess on how to index
112 # XXX need to apply \ quoting on '!!
116 p += "['%s']" % piece
119 def store(path, object):
120 if result_type == 'VALUE':
121 result.append(object)
122 elif result_type == 'IPATH': # Index format path (Python ext)
123 # return list of list of indices -- can be used w/o "eval" or split
124 result.append(path.split(';')[1:])
126 result.append(as_path(path))
129 def trace(expr, obj, path):
131 print("trace", expr, "/", path)
137 print("\t", loc, type(obj))
139 def f03(key, loc, expr, obj, path):
141 print("\tf03", key, loc, expr, path)
142 trace(s(key, expr), obj, path)
144 walk(loc, x, obj, path, f03)
148 def f04(key, loc, expr, obj, path):
150 print("\tf04", key, loc, expr, path)
151 if isinstance(obj, dict):
153 trace(s('..', expr), obj[key], s(path, key))
156 trace(s('..', expr), obj[key], s(path, key))
158 walk(loc, x, obj, path, f04)
160 # Perl jsonpath extension: return keys
161 def f06(key, loc, expr, obj, path):
162 if isinstance(obj, dict):
163 trace(expr, key, path)
165 walk(loc, x, obj, path, f06)
166 elif isinstance(obj, dict) and loc in obj:
167 trace(x, obj[loc], s(path, loc))
168 elif isinstance(obj, list) and isint(loc):
171 trace(x, obj[iloc], s(path, loc))
173 # [(index_expression)]
174 if loc.startswith("(") and loc.endswith(")"):
178 trace(s(e, x), obj, path)
181 # ?(filter_expression)
182 if loc.startswith("?(") and loc.endswith(")"):
186 def f05(key, loc, expr, obj, path):
188 print("f05", key, loc, expr, path)
189 if isinstance(obj, dict):
190 eval_result = evalx(loc, obj[key])
192 eval_result = evalx(loc, obj[int(key)])
194 trace(s(key, expr), obj, path)
197 walk(loc, x, obj, path, f05)
200 m = re.match(r'(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$', loc)
202 if isinstance(obj, (dict, list)):
218 # XXX int("badstr") raises exception
219 start = int(s0) if s0 else 0
220 end = int(s1) if s1 else objlen
221 step = int(s2) if s2 else 1
224 start = max(0, start + objlen)
226 start = min(objlen, start)
228 end = max(0, end + objlen)
230 end = min(objlen, end)
232 for i in xrange(start, end, step):
233 trace(s(i, x), obj, path)
236 # after (expr) & ?(expr)
237 if loc.find(",") >= 0:
239 for piece in re.split(r"'?,'?", loc):
241 print("piece", piece)
242 trace(s(piece, x), obj, path)
246 def walk(loc, expr, obj, path, funct):
247 if isinstance(obj, list):
248 for i in xrange(0, len(obj)):
249 funct(i, loc, expr, obj, path)
250 elif isinstance(obj, dict):
252 funct(key, loc, expr, obj, path)
255 """eval expression"""
260 # a nod to JavaScript. doesn't work for @.name.name.length
261 # Write len(@.name.name) instead!!!
262 loc = loc.replace("@.length", "len(__obj)")
264 loc = loc.replace("&&", " and ").replace("||", " or ")
266 # replace !@.name with 'name' not in obj
267 # XXX handle !@.name.name.name....
269 return "'%s' not in __obj" % m.group(1)
271 loc = re.sub("!@\.([a-zA-Z@_]+)", notvar, loc)
273 # replace @.name.... with __obj['name']....
274 # handle @.name[.name...].length
280 ret += "[%s]" % e # ain't necessarily so
282 ret += "['%s']" % e # XXX beware quotes!!!!
287 if elts[-1] == "length":
288 return "len(%s)" % brackets(elts[1:-1])
289 return brackets(elts[1:])
291 loc = re.sub(r'(?<!\\)(@\.[a-zA-Z@_.]+)', varmatch, loc)
293 # removed = -> == translation
294 # causes problems if a string contains =
296 # replace @ w/ "__obj", but \@ means a literal @
297 loc = re.sub(r'(?<!\\)@', "__obj", loc).replace(r'\@', '@')
300 print("eval disabled")
301 raise Exception("eval disabled")
305 # eval w/ caller globals, w/ local "__obj"!
306 v = eval(loc, caller_globals, {'__obj': obj})
307 except Exception as e:
318 # Get caller globals so eval can pick up user functions!!!
319 caller_globals = sys._getframe(1).f_globals
322 cleaned_expr = normalize(expr)
323 if cleaned_expr.startswith("$;"):
324 cleaned_expr = cleaned_expr[2:]
326 # XXX wrap this in a try??
327 trace(cleaned_expr, obj, '$')
334 if __name__ == '__main__':
338 import simplejson as json
342 # XXX take options for output format, output file, debug level
344 if len(sys.argv) < 3 or len(sys.argv) > 4:
345 sys.stdout.write("Usage: jsonpath.py FILE PATH [OUTPUT_TYPE]\n")
348 object = json.load(file(sys.argv[1]))
352 if len(sys.argv) > 3:
356 value = jsonpath(object, path, format)
362 json.dump(value, f, sort_keys=True, indent=1)