1010from typing import Any
1111from typing import Iterable
1212from typing import Mapping
13+ from typing import Optional
1314from typing import Sequence
1415from typing import Tuple
1516from typing import Union
@@ -58,14 +59,14 @@ class JSONPointer:
5859 max_int_index (int): The maximum integer allowed when resolving array
5960 items by index. Defaults to `(2**53) - 1`.
6061 min_int_index (int): The minimum integer allowed when resolving array
61- items by index. Defaults to `-(2**53) + 1 `.
62+ items by index. Defaults to `0 `.
6263 """
6364
6465 __slots__ = ("_s" , "parts" )
6566
6667 keys_selector = "~"
6768 max_int_index = (2 ** 53 ) - 1
68- min_int_index = - ( 2 ** 53 ) + 1
69+ min_int_index = 0
6970
7071 def __init__ (
7172 self ,
@@ -75,11 +76,15 @@ def __init__(
7576 unicode_escape : bool = True ,
7677 uri_decode : bool = False ,
7778 ) -> None :
78- self .parts = parts or self ._parse (
79- pointer ,
80- unicode_escape = unicode_escape ,
81- uri_decode = uri_decode ,
82- )
79+ if parts :
80+ self .parts = tuple (str (part ) for part in parts )
81+ else :
82+ self .parts = self ._parse (
83+ pointer ,
84+ unicode_escape = unicode_escape ,
85+ uri_decode = uri_decode ,
86+ )
87+
8388 self ._s = self ._encode (self .parts )
8489
8590 def __str__ (self ) -> str :
@@ -91,7 +96,7 @@ def _parse(
9196 * ,
9297 unicode_escape : bool ,
9398 uri_decode : bool ,
94- ) -> Tuple [Union [ int , str ] , ...]:
99+ ) -> Tuple [str , ...]:
95100 if uri_decode :
96101 s = unquote (s )
97102 if unicode_escape :
@@ -103,43 +108,49 @@ def _parse(
103108 "pointer must start with a slash or be the empty string"
104109 )
105110
106- return tuple (
107- self ._index (p .replace ("~1" , "/" ).replace ("~0" , "~" )) for p in s .split ("/" )
108- )[1 :]
111+ return tuple (p .replace ("~1" , "/" ).replace ("~0" , "~" ) for p in s .split ("/" ))[1 :]
112+
113+ def _index (self , key : str ) -> Optional [int ]:
114+ """Return an array index for `key`.
109115
110- def _index (self , s : str ) -> Union [str , int ]:
111- # Reject non-zero ints that start with a zero and negative integers.
112- if len (s ) > 1 and s .startswith (("0" , "-" )):
113- return s
116+ Return `None` if key can't be converted to an index.
117+ """
118+ # Reject indexes that start with a zero.
119+ if len (key ) > 1 and key .startswith ("0" ):
120+ return None
114121
115122 try :
116- index = int (s )
117- if index < self .min_int_index or index > self .max_int_index :
118- raise JSONPointerError ("index out of range" )
119- return index
123+ index = int (key )
120124 except ValueError :
121- return s
125+ return None
122126
123- def _getitem (self , obj : Any , key : Any ) -> Any :
127+ if index < self .min_int_index or index > self .max_int_index :
128+ raise JSONPointerIndexError (
129+ f"array indices must be between { self .min_int_index } "
130+ f" and { self .max_int_index } "
131+ )
132+
133+ return index
134+
135+ def _getitem (self , obj : Any , key : str ) -> Any :
124136 try :
125137 # Handle the most common cases. A mapping with a string key, or a sequence
126138 # with an integer index.
127- #
128- # Note that `obj` does not have to be a Mapping or Sequence here. Any object
129- # implementing `__getitem__` will do.
139+ if isinstance (obj , Sequence ) and not isinstance (obj , str ):
140+ index = self ._index (key )
141+ if isinstance (index , int ):
142+ return getitem (obj , index )
130143 return getitem (obj , key )
131144 except KeyError as err :
132145 return self ._handle_key_error (obj , key , err )
133146 except TypeError as err :
134147 return self ._handle_type_error (obj , key , err )
135148 except IndexError as err :
136- raise JSONPointerIndexError (f"index out of range: { key } " ) from err
137-
138- def _handle_key_error (self , obj : Any , key : Any , err : Exception ) -> object :
139- if isinstance (key , int ):
140- # Try a string repr of the index-like item as a mapping key.
141- return self ._getitem (obj , str (key ))
149+ if not isinstance (err , JSONPointerIndexError ):
150+ raise JSONPointerIndexError (f"index out of range: { key } " ) from err
151+ raise
142152
153+ def _handle_key_error (self , obj : Any , key : str , err : Exception ) -> object :
143154 # Handle non-standard key/property selector/pointer.
144155 #
145156 # For the benefit of `RelativeJSONPointer.to()` and `JSONPathMatch.pointer()`,
@@ -149,26 +160,18 @@ def _handle_key_error(self, obj: Any, key: Any, err: Exception) -> object:
149160 # Note that if a key with a leading `#`/`~` exists in `obj`, it will have been
150161 # handled by `_getitem`.
151162 if (
152- isinstance (key , str )
153- and isinstance (obj , Mapping )
163+ isinstance (obj , Mapping )
154164 and key .startswith ((self .keys_selector , "#" ))
155165 and key [1 :] in obj
156166 ):
157167 return key [1 :]
158168
159169 raise JSONPointerKeyError (key ) from err
160170
161- def _handle_type_error (self , obj : Any , key : Any , err : Exception ) -> object :
162- if (
163- isinstance (obj , str )
164- or not isinstance (obj , Sequence )
165- or not isinstance (key , str )
166- ):
171+ def _handle_type_error (self , obj : Any , key : str , err : Exception ) -> object :
172+ if not isinstance (obj , Sequence ) or not isinstance (key , str ):
167173 raise JSONPointerTypeError (f"{ key } : { err } " ) from err
168174
169- # `obj` is array-like
170- # `key` is a string
171-
172175 if key == "-" :
173176 # "-" is a valid index when appending to a JSON array with JSON Patch, but
174177 # not when resolving a JSON Pointer.
@@ -185,16 +188,6 @@ def _handle_type_error(self, obj: Any, key: Any, err: Exception) -> object:
185188 raise JSONPointerIndexError (f"index out of range: { _index } " ) from err
186189 return _index
187190
188- # Try int index. Reject non-zero ints that start with a zero.
189- index = self ._index (key )
190- if isinstance (index , int ):
191- return self ._getitem (obj , index )
192-
193- if re .match (r"-[0-9]+" , index ):
194- raise JSONPointerIndexError (
195- f"{ key } : array indices must be positive integers or zero"
196- ) from err
197-
198191 raise JSONPointerTypeError (f"{ key } : { err } " ) from err
199192
200193 def resolve (
@@ -354,13 +347,13 @@ def is_relative_to(self, other: JSONPointer) -> bool:
354347 )
355348
356349 def __eq__ (self , other : object ) -> bool :
357- return isinstance (other , JSONPointer ) and self .parts == other .parts
350+ return isinstance (other , self . __class__ ) and self .parts == other .parts
358351
359352 def __hash__ (self ) -> int :
360- return hash (self .parts ) # pragma: no cover
353+ return hash (( self .__class__ , self . parts ) ) # pragma: no cover
361354
362355 def __repr__ (self ) -> str :
363- return f"JSONPointer ({ self ._s !r} )" # pragma: no cover
356+ return f"{ self . __class__ . __name__ } ({ self ._s !r} )" # pragma: no cover
364357
365358 def exists (
366359 self , data : Union [str , IOBase , Sequence [object ], Mapping [str , object ]]
@@ -396,7 +389,7 @@ def parent(self) -> JSONPointer:
396389 if not self .parts :
397390 return self
398391 parent_parts = self .parts [:- 1 ]
399- return JSONPointer (
392+ return self . __class__ (
400393 self ._encode (parent_parts ),
401394 parts = parent_parts ,
402395 unicode_escape = False ,
@@ -420,14 +413,13 @@ def __truediv__(self, other: object) -> JSONPointer:
420413
421414 other = self ._unicode_escape (other .lstrip ())
422415 if other .startswith ("/" ):
423- return JSONPointer (other , unicode_escape = False , uri_decode = False )
416+ return self . __class__ (other , unicode_escape = False , uri_decode = False )
424417
425418 parts = self .parts + tuple (
426- self ._index (p .replace ("~1" , "/" ).replace ("~0" , "~" ))
427- for p in other .split ("/" )
419+ p .replace ("~1" , "/" ).replace ("~0" , "~" ) for p in other .split ("/" )
428420 )
429421
430- return JSONPointer (
422+ return self . __class__ (
431423 self ._encode (parts ), parts = parts , unicode_escape = False , uri_decode = False
432424 )
433425
@@ -617,7 +609,7 @@ def to(
617609 raise RelativeJSONPointerIndexError (
618610 f"index offset out of range { new_index } "
619611 )
620- parts [- 1 ] = int (parts [- 1 ]) + self .index
612+ parts [- 1 ] = str ( int (parts [- 1 ]) + self .index )
621613
622614 # Pointer or index/property
623615 if isinstance (self .pointer , JSONPointer ):
0 commit comments