Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 40 additions & 8 deletions jsonpath/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Author : zhangxianbing
Date : 2020-12-27 09:22:14
LastEditors : zhangxianbing
LastEditTime : 2021-03-02 15:56:00
LastEditTime : 2021-03-02 19:53:38
Description : JSONPath
"""
__version__ = "1.0.4"
Expand All @@ -12,6 +12,7 @@
import logging
import os
import re
import sys
from collections import defaultdict
from typing import Union

Expand Down Expand Up @@ -57,6 +58,8 @@ class JSONPath:
# save special patterns
REP_GET_QUOTE = re.compile(r"['](.*?)[']")
REP_PUT_QUOTE = re.compile(r"#Q(\d+)")
REP_GET_BACKQUOTE = re.compile(r"[`](.*?)[`]")
REP_PUT_BACKQUOTE = re.compile(r"#BQ(\d+)")
REP_GET_BRACKET = re.compile(r"[\[](.*?)[\]]")
REP_PUT_BRACKET = re.compile(r"#B(\d+)")
REP_GET_PAREN = re.compile(r"[\(](.*?)[\)]")
Expand All @@ -70,6 +73,7 @@ class JSONPath:
)

# annotations
f: list
segments: list
lpath: int
subx = defaultdict(list)
Expand All @@ -82,6 +86,8 @@ def __init__(self, expr: str):
self.lpath = len(self.segments)
logger.debug(f"segments : {self.segments}")

self.caller_globals = sys._getframe(1).f_globals

def parse(self, obj, result_type="VALUE"):
if not isinstance(obj, (list, dict)):
raise TypeError("obj must be a list or a dict.")
Expand All @@ -99,21 +105,26 @@ def parse(self, obj, result_type="VALUE"):

def _parse_expr(self, expr):
logger.debug(f"before expr : {expr}")

# pick up special patterns
expr = JSONPath.REP_GET_QUOTE.sub(self._get_quote, expr)
expr = JSONPath.REP_GET_BACKQUOTE.sub(self._get_backquote, expr)
expr = JSONPath.REP_GET_BRACKET.sub(self._get_bracket, expr)
expr = JSONPath.REP_GET_PAREN.sub(self._get_paren, expr)
# split
expr = JSONPath.REP_DOUBLEDOT.sub(f"{JSONPath.SEP}..{JSONPath.SEP}", expr)
expr = JSONPath.REP_DOT.sub(JSONPath.SEP, expr)
# put back
expr = JSONPath.REP_PUT_PAREN.sub(self._put_paren, expr)
expr = JSONPath.REP_PUT_BRACKET.sub(self._put_bracket, expr)
expr = JSONPath.REP_PUT_BACKQUOTE.sub(self._put_backquote, expr)
expr = JSONPath.REP_PUT_QUOTE.sub(self._put_quote, expr)
if expr.startswith("$;"):
expr = expr[2:]

logger.debug(f"after expr : {expr}")
return expr

# TODO abstract get and put procedures
def _get_quote(self, m):
n = len(self.subx["#Q"])
self.subx["#Q"].append(m.group(1))
Expand All @@ -122,6 +133,14 @@ def _get_quote(self, m):
def _put_quote(self, m):
return self.subx["#Q"][int(m.group(1))]

def _get_backquote(self, m):
n = len(self.subx["#BQ"])
self.subx["#BQ"].append(m.group(1))
return f"`#BQ{n}`"

def _put_backquote(self, m):
return self.subx["#BQ"][int(m.group(1))]

def _get_bracket(self, m):
n = len(self.subx["#B"])
self.subx["#B"].append(m.group(1))
Expand All @@ -139,7 +158,7 @@ def _put_paren(self, m):
return self.subx["#P"][int(m.group(1))]

@staticmethod
def _f_brackets(m):
def _gen_obj(m):
ret = "__obj"
for e in m.group(1).split("."):
ret += '["%s"]' % e
Expand All @@ -155,26 +174,39 @@ def _traverse(f, obj, i: int, path: str, *args):
f(v, i, f"{path}{JSONPath.SEP}{k}", *args)

@staticmethod
def _getattr(obj: dict, path: str):
def _getattr(obj: dict, path: str, *, convert_number_str=False):
r = obj
for k in path.split("."):
try:
r = r.get(k)
except (AttributeError, KeyError) as err:
logger.error(err)
return None

if convert_number_str and isinstance(r, str):
try:
if r.isdigit():
return int(r)
return float(r)
except ValueError:
pass
return r

@staticmethod
def _sorter(obj, sortbys):
for sortby in sortbys.split(",")[::-1]:
if sortby.startswith("~"):
obj.sort(
key=lambda t, k=sortby: JSONPath._getattr(t[1], k[1:]), reverse=True
key=lambda t, k=sortby: JSONPath._getattr(
t[1], k[1:], convert_number_str=True
),
reverse=True,
)
else:
obj.sort(key=lambda t, k=sortby: JSONPath._getattr(t[1], k))
obj.sort(
key=lambda t, k=sortby: JSONPath._getattr(
t[1], k, convert_number_str=True
)
)

def _filter(self, obj, i: int, path: str, step: str):
r = False
Expand Down Expand Up @@ -245,7 +277,7 @@ def _trace(self, obj, i: int, path):
# filter
if step.startswith("?(") and step.endswith(")"):
step = step[2:-1]
step = JSONPath.REP_FILTER_CONTENT.sub(self._f_brackets, step)
step = JSONPath.REP_FILTER_CONTENT.sub(self._gen_obj, step)
self._traverse(self._filter, obj, i + 1, path, step)
return

Expand Down