mirror of
https://github.com/cerbero90/json-parser.git
synced 2025-01-17 04:58: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
|
### 🐼 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.
|
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:
|
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
|
```php
|
||||||
|
@ -246,4 +246,17 @@ final class JsonParser implements IteratorAggregate
|
|||||||
|
|
||||||
return $this;
|
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 */
|
/** @var string|int $key */
|
||||||
$key = $this->decoder->decode($state->tree->currentKey());
|
$key = $this->decoder->decode($state->tree->currentKey());
|
||||||
$value = $this->decoder->decode($state->value());
|
$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();
|
$value instanceof self && $value->fastForward();
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ use Cerbero\JsonParser\Exceptions\DecodingException;
|
|||||||
use Cerbero\JsonParser\Exceptions\SyntaxException;
|
use Cerbero\JsonParser\Exceptions\SyntaxException;
|
||||||
use Cerbero\JsonParser\Pointers\Pointer;
|
use Cerbero\JsonParser\Pointers\Pointer;
|
||||||
use Cerbero\JsonParser\Pointers\Pointers;
|
use Cerbero\JsonParser\Pointers\Pointers;
|
||||||
|
use Cerbero\JsonParser\Tokens\Parser;
|
||||||
use Closure;
|
use Closure;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,6 +54,13 @@ final class Config
|
|||||||
*/
|
*/
|
||||||
public Closure $onSyntaxError;
|
public Closure $onSyntaxError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The callback to run for wrapping the parser.
|
||||||
|
*
|
||||||
|
* @var Closure
|
||||||
|
*/
|
||||||
|
public Closure $wrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiate the class
|
* Instantiate the class
|
||||||
*
|
*
|
||||||
@ -63,6 +71,7 @@ final class Config
|
|||||||
$this->pointers = new Pointers();
|
$this->pointers = new Pointers();
|
||||||
$this->onDecodingError = fn (DecodedValue $decoded) => throw new DecodingException($decoded);
|
$this->onDecodingError = fn (DecodedValue $decoded) => throw new DecodingException($decoded);
|
||||||
$this->onSyntaxError = fn (SyntaxException $e) => throw $e;
|
$this->onSyntaxError = fn (SyntaxException $e) => throw $e;
|
||||||
|
$this->wrapper = fn (Parser $parser) => $parser;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
use Cerbero\JsonParser\Dataset;
|
use Cerbero\JsonParser\Dataset;
|
||||||
use Cerbero\JsonParser\Decoders\SimdjsonDecoder;
|
use Cerbero\JsonParser\Decoders\SimdjsonDecoder;
|
||||||
use Cerbero\JsonParser\JsonParser;
|
use Cerbero\JsonParser\JsonParser;
|
||||||
|
use Cerbero\JsonParser\Tokens\Parser;
|
||||||
|
use Pest\Expectation;
|
||||||
|
|
||||||
use function Cerbero\JsonParser\parseJson;
|
use function Cerbero\JsonParser\parseJson;
|
||||||
|
|
||||||
@ -42,3 +44,9 @@ it('shows the progress while parsing', function () {
|
|||||||
|
|
||||||
expect($parser->progress()->percentage())->toBe(100.0);
|
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
|
<?php
|
||||||
|
|
||||||
use Cerbero\JsonParser\Tokens\Parser;
|
use Cerbero\JsonParser\Tokens\Parser;
|
||||||
|
use Pest\Expectation;
|
||||||
|
|
||||||
if (!function_exists('fixture')) {
|
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
|
* 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);
|
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