1 | <?php |
||
2 | |||
3 | namespace Cerbero\JsonParser; |
||
4 | |||
5 | use Cerbero\JsonParser\Decoders\DecodedValue; |
||
6 | use Cerbero\JsonParser\Decoders\Decoder; |
||
7 | use Cerbero\JsonParser\Exceptions\SyntaxException; |
||
8 | use Cerbero\JsonParser\Pointers\Pointer; |
||
9 | use Cerbero\JsonParser\Sources\AnySource; |
||
10 | use Cerbero\JsonParser\Tokens\Lexer; |
||
11 | use Cerbero\JsonParser\Tokens\Parser; |
||
12 | use Cerbero\JsonParser\ValueObjects\Config; |
||
13 | use Cerbero\JsonParser\ValueObjects\Progress; |
||
14 | use Closure; |
||
15 | use IteratorAggregate; |
||
16 | use Traversable; |
||
17 | |||
18 | /** |
||
19 | * The JSON parser entry-point. |
||
20 | * |
||
21 | * @implements IteratorAggregate<string|int, mixed> |
||
22 | */ |
||
23 | final class JsonParser implements IteratorAggregate |
||
24 | { |
||
25 | /** |
||
26 | * The configuration. |
||
27 | * |
||
28 | * @var Config |
||
29 | */ |
||
30 | private readonly Config $config; |
||
31 | |||
32 | /** |
||
33 | * The lexer. |
||
34 | * |
||
35 | * @var Lexer |
||
36 | */ |
||
37 | private readonly Lexer $lexer; |
||
38 | |||
39 | /** |
||
40 | * The parser. |
||
41 | * |
||
42 | * @var Parser |
||
43 | */ |
||
44 | private readonly Parser $parser; |
||
45 | |||
46 | /** |
||
47 | * Instantiate the class statically |
||
48 | * |
||
49 | * @param mixed $source |
||
50 | * @return self |
||
51 | */ |
||
52 | 348 | public static function parse(mixed $source): self |
|
53 | { |
||
54 | 348 | return new self($source); |
|
55 | } |
||
56 | |||
57 | /** |
||
58 | * Instantiate the class. |
||
59 | * |
||
60 | * @param mixed $source |
||
61 | */ |
||
62 | 373 | public function __construct(mixed $source) |
|
63 | { |
||
64 | 373 | $this->config = new Config(); |
|
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
65 | 373 | $this->lexer = new Lexer(new AnySource($source, $this->config)); |
|
0 ignored issues
–
show
|
|||
66 | 373 | $this->parser = new Parser($this->lexer->getIterator(), $this->config); |
|
0 ignored issues
–
show
|
|||
67 | } |
||
68 | |||
69 | /** |
||
70 | * Retrieve the lazily iterable JSON |
||
71 | * |
||
72 | * @return Traversable<string|int, mixed> |
||
73 | */ |
||
74 | 170 | public function getIterator(): Traversable |
|
75 | { |
||
76 | try { |
||
77 | 170 | yield from $this->parser; |
|
78 | 15 | } catch (SyntaxException $e) { |
|
0 ignored issues
–
show
catch (\Cerbero\JsonPars...ons\SyntaxException $e) is not reachable.
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed. Unreachable code is most often the result of function fx() {
try {
doSomething();
return true;
}
catch (\Exception $e) {
return false;
}
return false;
}
In the above example, the last ![]() |
|||
79 | 11 | $e->setPosition($this->lexer->position()); |
|
80 | 11 | ($this->config->onSyntaxError)($e); |
|
81 | } |
||
82 | } |
||
83 | |||
84 | /** |
||
85 | * Set the JSON pointers |
||
86 | * |
||
87 | * @param string[]|array<string, Closure> $pointers |
||
88 | * @return self |
||
89 | */ |
||
90 | 69 | public function pointers(array $pointers): self |
|
91 | { |
||
92 | 69 | foreach ($pointers as $pointer => $callback) { |
|
93 | 69 | $callback instanceof Closure ? $this->pointer($pointer, $callback) : $this->pointer($callback); |
|
94 | } |
||
95 | |||
96 | 61 | return $this; |
|
97 | } |
||
98 | |||
99 | /** |
||
100 | * Set a JSON pointer |
||
101 | * |
||
102 | * @param string $pointer |
||
103 | * @param ?Closure $callback |
||
104 | * @return self |
||
105 | */ |
||
106 | 201 | public function pointer(string $pointer, ?Closure $callback = null): self |
|
107 | { |
||
108 | 201 | $this->config->pointers->add(new Pointer($pointer, false, $callback)); |
|
109 | |||
110 | 197 | return $this; |
|
111 | } |
||
112 | |||
113 | /** |
||
114 | * Set the lazy JSON pointers |
||
115 | * |
||
116 | * @param string[]|array<string, Closure> $pointers |
||
117 | * @return self |
||
118 | */ |
||
119 | 32 | public function lazyPointers(array $pointers): self |
|
120 | { |
||
121 | 32 | foreach ($pointers as $pointer => $callback) { |
|
122 | 32 | $callback instanceof Closure ? $this->lazyPointer($pointer, $callback) : $this->lazyPointer($callback); |
|
123 | } |
||
124 | |||
125 | 32 | return $this; |
|
126 | } |
||
127 | |||
128 | /** |
||
129 | * Set a lazy JSON pointer |
||
130 | * |
||
131 | * @param string $pointer |
||
132 | * @param ?Closure $callback |
||
133 | * @return self |
||
134 | */ |
||
135 | 105 | public function lazyPointer(string $pointer, ?Closure $callback = null): self |
|
136 | { |
||
137 | 105 | $this->config->pointers->add(new Pointer($pointer, true, $callback)); |
|
138 | |||
139 | 105 | return $this; |
|
140 | } |
||
141 | |||
142 | /** |
||
143 | * Set a lazy JSON pointer for the whole JSON |
||
144 | * |
||
145 | * @return self |
||
146 | */ |
||
147 | 4 | public function lazy(): self |
|
148 | { |
||
149 | 4 | return $this->lazyPointer(''); |
|
150 | } |
||
151 | |||
152 | /** |
||
153 | * Traverse the JSON one key and value at a time |
||
154 | * |
||
155 | * @param ?Closure $callback |
||
156 | * @return void |
||
157 | */ |
||
158 | 16 | public function traverse(?Closure $callback = null): void |
|
159 | { |
||
160 | 16 | foreach ($this as $key => $value) { |
|
161 | 9 | $callback && $callback($value, $key, $this); |
|
162 | } |
||
163 | } |
||
164 | |||
165 | /** |
||
166 | * Eager load the JSON into an array |
||
167 | * |
||
168 | * @return array<string|int, mixed> |
||
169 | */ |
||
170 | 191 | public function toArray(): array |
|
171 | { |
||
172 | 191 | return $this->parser->toArray(); |
|
173 | } |
||
174 | |||
175 | /** |
||
176 | * Set the JSON decoder |
||
177 | * |
||
178 | * @param Decoder $decoder |
||
179 | * @return self |
||
180 | */ |
||
181 | 6 | public function decoder(Decoder $decoder): self |
|
182 | { |
||
183 | 6 | $this->config->decoder = $decoder; |
|
184 | |||
185 | 6 | return $this; |
|
186 | } |
||
187 | |||
188 | /** |
||
189 | * Retrieve the parsing progress |
||
190 | * |
||
191 | * @return Progress |
||
192 | */ |
||
193 | 1 | public function progress(): Progress |
|
194 | { |
||
195 | 1 | return $this->lexer->progress(); |
|
196 | } |
||
197 | |||
198 | /** |
||
199 | * The number of bytes to read in each chunk |
||
200 | * |
||
201 | * @param int<1, max> $bytes |
||
202 | * @return self |
||
203 | */ |
||
204 | 6 | public function bytes(int $bytes): self |
|
205 | { |
||
206 | 6 | $this->config->bytes = $bytes; |
|
207 | |||
208 | 6 | return $this; |
|
209 | } |
||
210 | |||
211 | /** |
||
212 | * Set the patch to apply during a decoding error |
||
213 | * |
||
214 | * @param mixed $patch |
||
215 | * @return self |
||
216 | */ |
||
217 | 4 | public function patchDecodingError(mixed $patch = null): self |
|
218 | { |
||
219 | 4 | return $this->onDecodingError(function (DecodedValue $decoded) use ($patch) { |
|
220 | 4 | $decoded->value = is_callable($patch) ? $patch($decoded) : $patch; |
|
221 | 4 | }); |
|
222 | } |
||
223 | |||
224 | /** |
||
225 | * Set the logic to run during a decoding error |
||
226 | * |
||
227 | * @param Closure $callback |
||
228 | * @return self |
||
229 | */ |
||
230 | 5 | public function onDecodingError(Closure $callback): self |
|
231 | { |
||
232 | 5 | $this->config->onDecodingError = $callback; |
|
233 | |||
234 | 5 | return $this; |
|
235 | } |
||
236 | |||
237 | /** |
||
238 | * Set the logic to run during a syntax error |
||
239 | * |
||
240 | * @param Closure $callback |
||
241 | * @return self |
||
242 | */ |
||
243 | 1 | public function onSyntaxError(Closure $callback): self |
|
244 | { |
||
245 | 1 | $this->config->onSyntaxError = $callback; |
|
246 | |||
247 | 1 | return $this; |
|
248 | } |
||
249 | |||
250 | /** |
||
251 | * Set the logic to run for wrapping the parser |
||
252 | * |
||
253 | * @param Closure $callback |
||
254 | * @return self |
||
255 | */ |
||
256 | 2 | public function wrap(Closure $callback): self |
|
257 | { |
||
258 | 2 | $this->config->wrapper = $callback; |
|
259 | |||
260 | 2 | return $this; |
|
261 | } |
||
262 | } |
||
263 |