|
28 | 28 |
|
29 | 29 |
|
30 | 30 | class Projection(Enum): |
31 | | - """Projection style.""" |
| 31 | + """Projection style used by `Query.select()`.""" |
32 | 32 |
|
33 | 33 | RELATIVE = auto() |
34 | 34 | ROOT = auto() |
35 | 35 | FLAT = auto() |
36 | 36 |
|
37 | 37 |
|
| 38 | +EMPTY = object() |
| 39 | + |
| 40 | + |
38 | 41 | class Query: |
39 | 42 | """A fluent API for managing `JSONPathMatch` iterators. |
40 | 43 |
|
@@ -157,40 +160,57 @@ def select( |
157 | 160 | Returns an iterable of objects built from selecting _expressions_ relative to |
158 | 161 | each match from the current query. |
159 | 162 | """ |
160 | | - for m in self._it: |
161 | | - if isinstance(m.obj, Sequence) or projection == Projection.FLAT: |
162 | | - obj: Union[List[Any], Dict[str, Any]] = [] |
163 | | - elif isinstance(m.obj, Mapping): |
164 | | - obj = {} |
| 163 | + return filter( |
| 164 | + bool, |
| 165 | + (self._select(m, expressions, projection) for m in self._it), |
| 166 | + ) |
| 167 | + |
| 168 | + def _select( |
| 169 | + self, |
| 170 | + match: JSONPathMatch, |
| 171 | + expressions: Tuple[str, ...], |
| 172 | + projection: Projection, |
| 173 | + ) -> object: |
| 174 | + if isinstance(match.obj, Sequence) or projection == Projection.FLAT: |
| 175 | + obj: Union[List[Any], Dict[str, Any]] = [] |
| 176 | + elif isinstance(match.obj, Mapping): |
| 177 | + obj = {} |
| 178 | + else: |
| 179 | + return None |
| 180 | + |
| 181 | + patch = JSONPatch() |
| 182 | + |
| 183 | + for expr in expressions: |
| 184 | + self._patch(match, expr, patch, projection) |
| 185 | + |
| 186 | + return patch.apply(obj) |
| 187 | + |
| 188 | + def _patch( |
| 189 | + self, |
| 190 | + match: JSONPathMatch, |
| 191 | + expr: str, |
| 192 | + patch: JSONPatch, |
| 193 | + projection: Projection, |
| 194 | + ) -> None: |
| 195 | + root_pointer = match.pointer() |
| 196 | + |
| 197 | + for rel_match in self._env.finditer(expr, match.obj): # type: ignore |
| 198 | + if projection == Projection.FLAT: |
| 199 | + patch.addap("/-", rel_match.obj) |
| 200 | + elif projection == Projection.ROOT: |
| 201 | + # Pointer string without a leading slash |
| 202 | + rel_pointer = "/".join( |
| 203 | + str(p).replace("~", "~0").replace("/", "~1") |
| 204 | + for p in rel_match.parts |
| 205 | + ) |
| 206 | + pointer = root_pointer / rel_pointer |
| 207 | + _patch_parents(pointer.parent(), patch, match.obj) # type: ignore |
| 208 | + patch.addap(pointer, rel_match.obj) |
165 | 209 | else: |
166 | | - return |
167 | | - |
168 | | - root_pointer = m.pointer() |
169 | | - patch = JSONPatch() |
170 | | - |
171 | | - for expr in expressions: |
172 | | - for match in self._env.finditer(expr, m.obj): # type: ignore |
173 | | - if projection == Projection.FLAT: |
174 | | - patch.addap("/-", match.obj) |
175 | | - elif projection == Projection.ROOT: |
176 | | - # Pointer string without a leading slash |
177 | | - rel_pointer = "/".join( |
178 | | - str(p).replace("~", "~0").replace("/", "~1") |
179 | | - for p in match.parts |
180 | | - ) |
181 | | - _pointer = root_pointer / rel_pointer |
182 | | - _patch_parents(_pointer.parent(), patch, m.obj) # type: ignore |
183 | | - patch.addap(_pointer, match.obj) |
184 | | - else: |
185 | | - # Natural projection |
186 | | - _pointer = match.pointer() |
187 | | - _patch_parents(_pointer.parent(), patch, m.obj) # type: ignore |
188 | | - patch.addap(_pointer, match.obj) |
189 | | - |
190 | | - patch.apply(obj) |
191 | | - |
192 | | - if obj: |
193 | | - yield obj |
| 210 | + # Natural projection |
| 211 | + pointer = rel_match.pointer() |
| 212 | + _patch_parents(pointer.parent(), patch, match.obj) # type: ignore |
| 213 | + patch.addap(pointer, rel_match.obj) |
194 | 214 |
|
195 | 215 | def first_one(self) -> Optional[JSONPathMatch]: |
196 | 216 | """Return the first `JSONPathMatch` or `None` if there were no matches.""" |
|
0 commit comments