1+ """JSONPath implementation for Python.
2+
3+ This module provides a lightweight JSONPath implementation with support for:
4+ - Standard JSONPath operators ($, @, ., .., *, [])
5+ - Filter expressions with comparison, membership, and regex operators
6+ - Sorter expressions for ordering results
7+ - Field extractor expressions
8+ - Value updates via JSONPath
9+
10+ Example:
11+ >>> from jsonpath import JSONPath, search
12+ >>> data = {"store": {"book": [{"price": 10}, {"price": 20}]}}
13+ >>> JSONPath("$..price").parse(data)
14+ [10, 20]
15+ >>> search("$.store.book[0].price", data)
16+ [10]
17+ """
18+
119import logging
220import os
321import re
@@ -29,14 +47,39 @@ def create_logger(name: str = None, level: Union[int, str] = logging.INFO):
2947
3048
3149class ExprSyntaxError (Exception ):
32- pass
50+ """Raised when a JSONPath expression has invalid syntax.
51+
52+ Examples of invalid syntax:
53+ - Using sorter on non-collection types
54+ - Using field-extractor on non-dict types
55+ """
3356
3457
3558class JSONPathTypeError (Exception ):
36- pass
59+ """Raised when type-related errors occur during JSONPath operations.
60+
61+ Examples:
62+ - Comparing incompatible types during sorting (e.g., str vs int)
63+ - Sorting with missing keys that result in None comparisons
64+ """
3765
3866
3967class JSONPath :
68+ """JSONPath expression parser and evaluator.
69+
70+ A JSONPath expression is used to navigate and extract data from JSON objects.
71+ This implementation supports extended syntax including filters, sorters, and
72+ field extractors.
73+
74+ Attributes:
75+ RESULT_TYPE: Supported result types ('VALUE' or 'PATH').
76+
77+ Example:
78+ >>> jp = JSONPath("$.store.book[?(@.price < 10)].title")
79+ >>> jp.parse({"store": {"book": [{"title": "A", "price": 5}]}})
80+ ['A']
81+ """
82+
4083 RESULT_TYPE = {
4184 "VALUE" : "A list of specific values." ,
4285 "PATH" : "All path of specific values." ,
@@ -71,6 +114,11 @@ class JSONPath:
71114 REP_DOTDOT_BRACKET = re .compile (r"\.(\.#B)" )
72115
73116 def __init__ (self , expr : str ):
117+ """Initialize JSONPath with an expression.
118+
119+ Args:
120+ expr: JSONPath expression string (e.g., "$.store.book[*].price")
121+ """
74122 # Initialize instance variables
75123 self .subx = defaultdict (list )
76124 self .segments = []
@@ -86,6 +134,22 @@ def __init__(self, expr: str):
86134 logger .debug (f"segments : { self .segments } " )
87135
88136 def parse (self , obj , result_type = "VALUE" , eval_func = eval ):
137+ """Parse JSON object using the JSONPath expression.
138+
139+ Args:
140+ obj: JSON object (dict or list) to parse
141+ result_type: Type of result to return
142+ - 'VALUE': Return matched values (default)
143+ - 'PATH': Return JSONPath strings of matched locations
144+ eval_func: Custom eval function for filter expressions (default: builtin eval)
145+
146+ Returns:
147+ List of matched values or paths depending on result_type
148+
149+ Raises:
150+ TypeError: If obj is not a dict or list
151+ ValueError: If result_type is invalid
152+ """
89153 if not isinstance (obj , (list , dict )):
90154 raise TypeError ("obj must be a list or a dict." )
91155
@@ -101,9 +165,15 @@ def parse(self, obj, result_type="VALUE", eval_func=eval):
101165 return self .result
102166
103167 def search (self , obj , result_type = "VALUE" ):
168+ """Alias for parse(). Search JSON object using the JSONPath expression."""
104169 return self .parse (obj , result_type )
105170
106171 def _parse_expr (self , expr ):
172+ """Parse and normalize JSONPath expression into segments.
173+
174+ Handles special patterns (quotes, brackets, parentheses) by temporarily
175+ replacing them with placeholders, then splits by dots and restores.
176+ """
107177 if logger .isEnabledFor (logging .DEBUG ):
108178 logger .debug (f"before expr : { expr } " )
109179 # pick up special patterns
@@ -237,6 +307,15 @@ def _extract_key_from_group(group: dict):
237307
238308 @staticmethod
239309 def _traverse (f , obj , i : int , path : str , * args ):
310+ """Traverse object children and apply function to each.
311+
312+ Args:
313+ f: Function to apply to each child element
314+ obj: Object to traverse (list or dict)
315+ i: Current segment index
316+ path: Current JSONPath string
317+ *args: Additional arguments to pass to function f
318+ """
240319 if isinstance (obj , list ):
241320 for idx , v in enumerate (obj ):
242321 f (v , i , JSONPath ._build_path (path , idx ), * args )
@@ -246,6 +325,16 @@ def _traverse(f, obj, i: int, path: str, *args):
246325
247326 @staticmethod
248327 def _getattr (obj : Any , path : str , * , convert_number_str = False ):
328+ """Get attribute value from object by dot-notation path.
329+
330+ Args:
331+ obj: Source object (dict)
332+ path: Dot-separated path string (e.g., "author.name")
333+ convert_number_str: If True, convert numeric strings to int/float
334+
335+ Returns:
336+ The value at the path, or _MISSING sentinel if not found
337+ """
249338 # Fast path for single key (most common case)
250339 if "." not in path :
251340 if isinstance (obj , dict ) and path in obj :
@@ -295,6 +384,14 @@ def key_func(t, k):
295384 raise JSONPathTypeError (f"not possible to compare str and int when sorting: { e } " ) from e
296385
297386 def _filter (self , obj , i : int , path : str , step : str ):
387+ """Evaluate filter expression and continue trace if condition is true.
388+
389+ Args:
390+ obj: Current object to evaluate against filter
391+ i: Next segment index to trace
392+ path: Current JSONPath string
393+ step: Python expression string to evaluate
394+ """
298395 r = False
299396 try :
300397 r = self .eval_func (step , None , {"__obj" : obj , "RegexPattern" : RegexPattern })
@@ -304,11 +401,15 @@ def _filter(self, obj, i: int, path: str, step: str):
304401 self ._trace (obj , i , path )
305402
306403 def _trace (self , obj , i : int , path ):
307- """Perform operation on object.
404+ """Recursively traverse object following JSONPath segments.
405+
406+ This is the core evaluation method that processes each segment of the
407+ parsed JSONPath expression and navigates through the object accordingly.
308408
309409 Args:
310- obj ([type]): current operating object
311- i (int): current operation specified by index in self.segments
410+ obj: Current object being traversed
411+ i: Index of current segment in self.segments
412+ path: JSONPath string representing current location
312413 """
313414
314415 # store
@@ -446,17 +547,43 @@ def update(self, obj: Union[list, dict], value_or_func: Union[Any, Callable[[Any
446547
447548
448549class RegexPattern :
550+ """Regex pattern wrapper for use with the =~ operator in filter expressions.
551+
552+ This class enables regex matching syntax like: @.name =~ /pattern/
553+ The @ operator is overloaded to perform the regex search.
554+
555+ Example:
556+ >>> pattern = RegexPattern(r"^test")
557+ >>> "testing" @ pattern
558+ True
559+ """
560+
449561 def __init__ (self , pattern ):
562+ """Initialize with a regex pattern string."""
450563 self .pattern = pattern
451564 self ._compiled = re .compile (pattern ) # Pre-compile for better performance
452565
453566 def __rmatmul__ (self , other ):
567+ """Right matmul operator (@) - checks if other matches the pattern."""
454568 if isinstance (other , str ):
455569 return bool (self ._compiled .search (other ))
456570 return False
457571
458572
459573def compile (expr ):
574+ """Compile a JSONPath expression for reuse.
575+
576+ Args:
577+ expr: JSONPath expression string
578+
579+ Returns:
580+ JSONPath object that can be used to parse multiple JSON objects
581+
582+ Example:
583+ >>> jp = compile("$.store.book[*].price")
584+ >>> jp.parse(data1)
585+ >>> jp.parse(data2)
586+ """
460587 return JSONPath (expr )
461588
462589
0 commit comments