Issues (13)

src/JsonParser.php (3 issues)

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
The property config is declared read-only in Cerbero\JsonParser\JsonParser.
Loading history...
65 373
        $this->lexer = new Lexer(new AnySource($source, $this->config));
0 ignored issues
show
The property lexer is declared read-only in Cerbero\JsonParser\JsonParser.
Loading history...
66 373
        $this->parser = new Parser($this->lexer->getIterator(), $this->config);
0 ignored issues
show
The property parser is declared read-only in Cerbero\JsonParser\JsonParser.
Loading history...
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) {
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