1- """A fluent API for managing JSONPathMatch iterators."""
1+ """A fluent API for working with ` JSONPathMatch` iterators."""
22
33from __future__ import annotations
44
@@ -154,6 +154,41 @@ def pointers(self) -> Iterable[JSONPointer]:
154154 """Return an iterable of JSONPointers, one for each match."""
155155 return (m .pointer () for m in self ._it )
156156
157+ def first_one (self ) -> Optional [JSONPathMatch ]:
158+ """Return the first `JSONPathMatch` or `None` if there were no matches."""
159+ try :
160+ return next (self ._it )
161+ except StopIteration :
162+ return None
163+
164+ def one (self ) -> Optional [JSONPathMatch ]:
165+ """Return the first `JSONPathMatch` or `None` if there were no matches.
166+
167+ `one()` is an alias for `first_one()`.
168+ """
169+ return self .first_one ()
170+
171+ def last_one (self ) -> Optional [JSONPathMatch ]:
172+ """Return the last `JSONPathMatch` or `None` if there were no matches."""
173+ try :
174+ return next (iter (self .tail (1 )))
175+ except StopIteration :
176+ return None
177+
178+ def tee (self , n : int = 2 ) -> Tuple [Query , ...]:
179+ """Return _n_ independent queries by teeing this query's iterator.
180+
181+ It is not safe to use a `Query` instance after calling `tee()`.
182+ """
183+ return tuple (Query (it , self ._env ) for it in itertools .tee (self ._it , n ))
184+
185+ def take (self , n : int ) -> Query :
186+ """Return a new query iterating over the next _n_ matches.
187+
188+ It is safe to continue using this query after calling take.
189+ """
190+ return Query (list (itertools .islice (self ._it , n )), self ._env )
191+
157192 def select (
158193 self ,
159194 * expressions : str ,
@@ -199,7 +234,7 @@ def _select(
199234 for expr in expressions :
200235 self ._patch (match , expr , patch , projection )
201236
202- return _sparse_values (patch .apply (obj ))
237+ return _fix_sparse_arrays (patch .apply (obj ))
203238
204239 def _patch (
205240 self ,
@@ -219,87 +254,51 @@ def _patch(
219254 str (p ).replace ("~" , "~0" ).replace ("/" , "~1" )
220255 for p in rel_match .parts
221256 )
222- pointer = _patch_parents (root_pointer / rel_pointer , patch , match .root ) # type: ignore
257+ pointer = root_pointer / rel_pointer
258+ _patch_parents (pointer .parent (), patch , match .root )
223259 patch .addap (pointer , rel_match .obj )
224260 else :
225261 # Natural projection
226- pointer = _patch_parents (rel_match .pointer (), patch , match .obj ) # type: ignore
262+ pointer = rel_match .pointer ()
263+ _patch_parents (pointer .parent (), patch , match .obj ) # type: ignore
227264 patch .addap (pointer , rel_match .obj )
228265
229- def first_one (self ) -> Optional [JSONPathMatch ]:
230- """Return the first `JSONPathMatch` or `None` if there were no matches."""
231- try :
232- return next (self ._it )
233- except StopIteration :
234- return None
235-
236- def one (self ) -> Optional [JSONPathMatch ]:
237- """Return the first `JSONPathMatch` or `None` if there were no matches.
238-
239- `one()` is an alias for `first_one()`.
240- """
241- return self .first_one ()
242-
243- def last_one (self ) -> Optional [JSONPathMatch ]:
244- """Return the last `JSONPathMatch` or `None` if there were no matches."""
245- try :
246- return next (iter (self .tail (1 )))
247- except StopIteration :
248- return None
249-
250- def tee (self , n : int = 2 ) -> Tuple [Query , ...]:
251- """Return _n_ independent queries by teeing this query's iterator.
252-
253- It is not safe to use a `Query` instance after calling `tee()`.
254- """
255- return tuple (Query (it , self ._env ) for it in itertools .tee (self ._it , n ))
256-
257- def take (self , n : int ) -> Query :
258- """Return a new query iterating over the next _n_ matches.
259-
260- It is safe to continue using this query after calling take.
261- """
262- return Query (list (itertools .islice (self ._it , n )), self ._env )
263-
264266
265267def _patch_parents (
266268 pointer : JSONPointer ,
267269 patch : JSONPatch ,
268270 obj : Union [Sequence [Any ], Mapping [str , Any ]],
269- ) -> JSONPointer :
270- parent = pointer .parent ()
271- if parent .parent ().parts :
272- _patch_parents (parent , patch , obj )
271+ ) -> None :
272+ if pointer .parent ().parts :
273+ _patch_parents (pointer .parent (), patch , obj )
273274
274- if parent .parts :
275+ if pointer .parts :
275276 try :
276- _obj = parent .resolve (obj )
277+ _obj = pointer .resolve (obj )
277278 except JSONPointerKeyError :
278279 _obj = obj
279280
280- # For lack of a better solution , we're patching arrays to dictionaries with
281- # integer keys. This is to handle sparse array selections without having to
282- # keep track of indexes and how they map from the root JSON value to the
283- # selected JSON value.
281+ # For lack of a better idea , we're patching arrays to dictionaries with
282+ # integer keys. This is to handle sparse array selections without having
283+ # to keep track of indexes and how they map from the root JSON value to
284+ # the selected JSON value.
284285 #
285286 # We'll fix these "sparse arrays" after the patch has been applied.
286287 if isinstance (_obj , (Sequence , Mapping )) and not isinstance (_obj , str ):
287- patch .addne (parent , {})
288-
289- return pointer
288+ patch .addne (pointer , {})
290289
291290
292- def _sparse_values (obj : Any ) -> object :
291+ def _fix_sparse_arrays (obj : Any ) -> object :
293292 """Fix sparse arrays (dictionaries with integer keys)."""
294293 if isinstance (obj , str ) or not obj :
295294 return obj
296295
297296 if isinstance (obj , Sequence ):
298- return [_sparse_values (e ) for e in obj ]
297+ return [_fix_sparse_arrays (e ) for e in obj ]
299298
300299 if isinstance (obj , Mapping ):
301300 if isinstance (next (iter (obj )), int ):
302- return [_sparse_values (v ) for v in obj .values ()]
303- return {k : _sparse_values (v ) for k , v in obj .items ()}
301+ return [_fix_sparse_arrays (v ) for v in obj .values ()]
302+ return {k : _fix_sparse_arrays (v ) for k , v in obj .items ()}
304303
305304 return obj
0 commit comments