mirror of
https://github.com/cerbero90/json-parser.git
synced 2025-01-16 20:48:15 +01:00
Add option to wrap the parser
This commit is contained in:
parent
5ce3f0744f
commit
ec84c4b703
19
README.md
19
README.md
@ -270,7 +270,7 @@ $array = JsonParser::parse($source)->pointers(['/results/0/gender', '/results/0/
|
||||
|
||||
### 🐼 Lazy pointers
|
||||
|
||||
JSON Parser only keeps one key and one value in memory at a time. However, if the value is a large array or object, it may be inefficient to keep it all in memory.
|
||||
JSON Parser only keeps one key and one value in memory at a time. However, if the value is a large array or object, it may be inefficient or even impossible to keep it all in memory.
|
||||
|
||||
To solve this problem, we can use lazy pointers. These pointers recursively keep in memory only one key and one value at a time for any nested array or object.
|
||||
|
||||
@ -323,6 +323,23 @@ foreach (JsonParser::parse($source)->lazy() as $key => $value) {
|
||||
}
|
||||
```
|
||||
|
||||
We can recursively wrap any instance of `Cerbero\JsonParser\Tokens\Parser` by chaining `wrap()`. This lets us wrap lazy loaded JSON arrays and objects into classes with advanced functionalities, like mapping or filtering:
|
||||
|
||||
```php
|
||||
$json = JsonParser::parse($source)
|
||||
->wrap(fn (Parser $parser) => new MyWrapper(fn () => yield from $parser))
|
||||
->lazy();
|
||||
|
||||
foreach ($json as $key => $value) {
|
||||
// 1st iteration: $key === 'results', $value instanceof MyWrapper
|
||||
foreach ($value as $nestedKey => $nestedValue) {
|
||||
// 1st iteration: $nestedKey === 0, $nestedValue instanceof MyWrapper
|
||||
// 2nd iteration: $nestedKey === 1, $nestedValue instanceof MyWrapper
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Lazy pointers also have all the other functionalities of normal pointers: they accept callbacks, can be set one by one or all together, can be eager loaded into an array and can be mixed with normal pointers as well:
|
||||
|
||||
```php
|
||||
|
@ -246,4 +246,17 @@ final class JsonParser implements IteratorAggregate
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the logic to run for wrapping the parser
|
||||
*
|
||||
* @param Closure $callback
|
||||
* @return self
|
||||
*/
|
||||
public function wrap(Closure $callback): self
|
||||
{
|
||||
$this->config->wrapper = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -71,8 +71,9 @@ final class Parser implements IteratorAggregate
|
||||
/** @var string|int $key */
|
||||
$key = $this->decoder->decode($state->tree->currentKey());
|
||||
$value = $this->decoder->decode($state->value());
|
||||
$wrapper = $value instanceof self ? ($this->config->wrapper)($value) : $value;
|
||||
|
||||
yield $key => $state->callPointer($value, $key);
|
||||
yield $key => $state->callPointer($wrapper, $key);
|
||||
|
||||
$value instanceof self && $value->fastForward();
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use Cerbero\JsonParser\Exceptions\DecodingException;
|
||||
use Cerbero\JsonParser\Exceptions\SyntaxException;
|
||||
use Cerbero\JsonParser\Pointers\Pointer;
|
||||
use Cerbero\JsonParser\Pointers\Pointers;
|
||||
use Cerbero\JsonParser\Tokens\Parser;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
@ -53,6 +54,13 @@ final class Config
|
||||
*/
|
||||
public Closure $onSyntaxError;
|
||||
|
||||
/**
|
||||
* The callback to run for wrapping the parser.
|
||||
*
|
||||
* @var Closure
|
||||
*/
|
||||
public Closure $wrapper;
|
||||
|
||||
/**
|
||||
* Instantiate the class
|
||||
*
|
||||
@ -63,6 +71,7 @@ final class Config
|
||||
$this->pointers = new Pointers();
|
||||
$this->onDecodingError = fn (DecodedValue $decoded) => throw new DecodingException($decoded);
|
||||
$this->onSyntaxError = fn (SyntaxException $e) => throw $e;
|
||||
$this->wrapper = fn (Parser $parser) => $parser;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3,6 +3,8 @@
|
||||
use Cerbero\JsonParser\Dataset;
|
||||
use Cerbero\JsonParser\Decoders\SimdjsonDecoder;
|
||||
use Cerbero\JsonParser\JsonParser;
|
||||
use Cerbero\JsonParser\Tokens\Parser;
|
||||
use Pest\Expectation;
|
||||
|
||||
use function Cerbero\JsonParser\parseJson;
|
||||
|
||||
@ -42,3 +44,9 @@ it('shows the progress while parsing', function () {
|
||||
|
||||
expect($parser->progress()->percentage())->toBe(100.0);
|
||||
});
|
||||
|
||||
it('wraps the parser recursively', function (string $source) {
|
||||
$json = JsonParser::parse($source)->wrap(fn (Parser $parser) => yield from $parser)->lazy();
|
||||
|
||||
expect($json)->traverse(fn (Expectation $value) => $value->toBeWrappedInto(Generator::class));
|
||||
})->with([fixture('json/complex_array.json'), fixture('json/complex_array.json')]);
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Cerbero\JsonParser\Tokens\Parser;
|
||||
use Pest\Expectation;
|
||||
|
||||
if (!function_exists('fixture')) {
|
||||
/**
|
||||
@ -15,6 +16,43 @@ if (!function_exists('fixture')) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expect the given sequence from a Traversable
|
||||
* Temporary fix to sequence() until this PR is merged: https://github.com/pestphp/pest/pull/895
|
||||
*
|
||||
* @param mixed ...$callbacks
|
||||
* @return Expectation
|
||||
*/
|
||||
expect()->extend('traverse', function (mixed ...$callbacks) {
|
||||
if (! is_iterable($this->value)) {
|
||||
throw new BadMethodCallException('Expectation value is not iterable.');
|
||||
}
|
||||
|
||||
if (empty($callbacks)) {
|
||||
throw new InvalidArgumentException('No sequence expectations defined.');
|
||||
}
|
||||
|
||||
$index = $valuesCount = 0;
|
||||
|
||||
foreach ($this->value as $key => $value) {
|
||||
$valuesCount++;
|
||||
|
||||
if ($callbacks[$index] instanceof Closure) {
|
||||
$callbacks[$index](new self($value), new self($key));
|
||||
} else {
|
||||
(new self($value))->toEqual($callbacks[$index]);
|
||||
}
|
||||
|
||||
$index = isset($callbacks[$index + 1]) ? $index + 1 : 0;
|
||||
}
|
||||
|
||||
if (count($callbacks) > $valuesCount) {
|
||||
throw new OutOfRangeException('Sequence expectations are more than the iterable items');
|
||||
}
|
||||
|
||||
return $this;
|
||||
});
|
||||
|
||||
/**
|
||||
* Expect that keys and values are parsed correctly
|
||||
*
|
||||
@ -74,4 +112,20 @@ expect()->extend('toLazyLoadRecursively', function (array $keys, array $expected
|
||||
expect($key)->toBe($expectedKey)->and($value)->toLazyLoadRecursively($keys, $expected);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
});
|
||||
|
||||
/**
|
||||
* Expect that all Parser instances are wrapped recursively
|
||||
*
|
||||
* @param string $wrapper
|
||||
* @return Expectation
|
||||
*/
|
||||
expect()->extend('toBeWrappedInto', function (string $wrapper) {
|
||||
return $this->when(is_object($this->value), fn (Expectation $value) => $value
|
||||
->toBeInstanceOf($wrapper)
|
||||
->not->toBeInstanceOf(Parser::class)
|
||||
->traverse(fn (Expectation $value) => $value->toBeWrappedInto($wrapper))
|
||||
);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user