1
0
mirror of https://github.com/halaxa/json-machine.git synced 2025-01-16 20:48:17 +01:00

NestedIterator replaced with RecursiveItems

This commit is contained in:
Filip Halaxa 2024-08-26 21:43:32 +02:00
parent 754d3609c0
commit cf83311f16
9 changed files with 286 additions and 136 deletions

View File

@ -10,21 +10,6 @@ use JsonMachine\JsonDecoder\ItemDecoder;
trait FacadeTrait
{
/**
* @var iterable
*/
private $chunks;
/**
* @var string
*/
private $jsonPointer;
/**
* @var ItemDecoder|null
*/
private $jsonDecoder;
/**
* @var Parser
*/
@ -35,100 +20,34 @@ trait FacadeTrait
*/
private $debugEnabled;
/**
* @todo Make private when PHP 7 stops being supported
*/
protected abstract function recursive(): bool;
public function isDebugEnabled(): bool
{
return $this->debugEnabled;
}
/**
* @param iterable $bytesIterator
*
* @throws InvalidArgumentException
*/
public function __construct($bytesIterator, array $options = [])
private static function createParser($bytesIterator, ItemsOptions $options, bool $recursive): Parser
{
$options = new ItemsOptions($options);
$this->chunks = $bytesIterator;
$this->jsonPointer = $options['pointer'];
$this->jsonDecoder = $options['decoder'];
$this->debugEnabled = $options['debug'];
if ($this->debugEnabled) {
if ($options['debug']) {
$tokensClass = TokensWithDebugging::class;
} else {
$tokensClass = Tokens::class;
}
$this->parser = new Parser(
return new Parser(
new $tokensClass(
$this->chunks
$bytesIterator
),
$this->jsonPointer,
$this->jsonDecoder ?: new ExtJsonDecoder(),
$this->recursive()
$options['pointer'],
$options['decoder'] ?: new ExtJsonDecoder(),
$recursive
);
}
/**
* @param string $string
*
* @return self
*
* @throws InvalidArgumentException
*/
public static function fromString($string, array $options = [])
{
return new self(new StringChunks($string), $options);
}
/**
* @param string $file
*
* @return self
*
* @throws Exception\InvalidArgumentException
*/
public static function fromFile($file, array $options = [])
{
return new self(new FileChunks($file), $options);
}
/**
* @param resource $stream
*
* @return self
*
* @throws Exception\InvalidArgumentException
*/
public static function fromStream($stream, array $options = [])
{
return new self(new StreamChunks($stream), $options);
}
/**
* @param iterable $iterable
*
* @return self
*
* @throws Exception\InvalidArgumentException
*/
public static function fromIterable($iterable, array $options = [])
{
return new self($iterable, $options);
}
/**
* @return \Generator
*
* @throws Exception\PathNotFoundException
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
return $this->parser->getIterator();
}
/**
* @throws Exception\JsonMachineException
*/
@ -159,10 +78,23 @@ trait FacadeTrait
}
/**
* @return bool
* @param string $string
*/
public function isDebugEnabled()
{
return $this->debugEnabled;
}
abstract public static function fromString($string, array $options = []): self;
/**
* @param string $file
*/
abstract public static function fromFile($file, array $options = []): self;
/**
* @param resource $stream
*/
abstract public static function fromStream($stream, array $options = []): self;
/**
* @param iterable $iterable
*/
abstract public static function fromIterable($iterable, array $options = []): self;
}

View File

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace JsonMachine;
use JsonMachine\Exception\InvalidArgumentException;
/**
* Entry-point facade for JSON Machine.
*/
@ -11,8 +13,67 @@ final class Items implements \IteratorAggregate, PositionAware
{
use FacadeTrait;
protected function recursive(): bool
/**
* @param iterable $bytesIterator
*
* @throws InvalidArgumentException
*/
public function __construct($bytesIterator, array $options = [])
{
return false;
$options = new ItemsOptions($options);
$this->debugEnabled = $options['debug'];
$this->parser = $this->createParser($bytesIterator, $options, false);
}
/**
* @param string $string
*
* @throws InvalidArgumentException
*/
public static function fromString($string, array $options = []): self
{
return new self(new StringChunks($string), $options);
}
/**
* @param string $file
*
* @throws Exception\InvalidArgumentException
*/
public static function fromFile($file, array $options = []): self
{
return new self(new FileChunks($file), $options);
}
/**
* @param resource $stream
*
* @throws Exception\InvalidArgumentException
*/
public static function fromStream($stream, array $options = []): self
{
return new self(new StreamChunks($stream), $options);
}
/**
* @param iterable $iterable
*
* @throws Exception\InvalidArgumentException
*/
public static function fromIterable($iterable, array $options = []): self
{
return new self($iterable, $options);
}
/**
* @return \Generator
*
* @throws Exception\PathNotFoundException
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
return $this->parser->getIterator();
}
}

View File

@ -163,14 +163,12 @@ class Parser implements \IteratorAggregate, PositionAware
)
) {
if ($this->recursive && ($token == '{' || $token == '[')) {
$jsonValue = new NestedIterator(
(new self(
$this->remainingTokens(),
'',
$this->jsonDecoder,
true
))->getIterator()
);
$jsonValue = (new self(
$this->remainingTokens(),
'',
$this->jsonDecoder,
true
))->$this->getIterator();
$token = ' ';
} else {
$jsonValue .= $token;
@ -400,7 +398,7 @@ class Parser implements \IteratorAggregate, PositionAware
*/
public function getMatchedJsonPointer(): string
{
if ($this->matchedJsonPointer === null) {
if ($this->isOutsideGenerator()) {
throw new JsonMachineException(__METHOD__.' must be called inside a loop');
}

View File

@ -4,15 +4,123 @@ declare(strict_types=1);
namespace JsonMachine;
use Iterator;
use JsonMachine\Exception\InvalidArgumentException;
/**
* Entry-point facade for recursive iteration.
*/
final class RecursiveItems implements \IteratorAggregate, PositionAware
final class RecursiveItems implements \RecursiveIterator, PositionAware
{
use FacadeTrait;
protected function recursive(): bool
/** @var Parser */
private $parser;
/** @var ItemsOptions */
private $options;
/** @var Iterator */
private $parserIterator;
public function __construct(Parser $parser, ItemsOptions $options)
{
return true;
$this->parser = $parser;
$this->options = $options;
$this->debugEnabled = $options['debug'];
}
/**
* @throws InvalidArgumentException
*/
public static function fromString($string, array $options = []): self
{
$options = new ItemsOptions($options);
return new self(
self::createParser(new StringChunks($string), $options, true),
$options
);
}
/**
* @throws InvalidArgumentException
*/
public static function fromFile($file, array $options = []): self
{
$options = new ItemsOptions($options);
return new self(
self::createParser(new FileChunks($file), $options, true),
$options
);
}
/**
* @throws InvalidArgumentException
*/
public static function fromStream($stream, array $options = []): self
{
$options = new ItemsOptions($options);
return new self(
self::createParser(new StreamChunks($stream), $options, true),
$options
);
}
/**
* @throws InvalidArgumentException
*/
public static function fromIterable($iterable, array $options = []): self
{
$options = new ItemsOptions($options);
return new self(
self::createParser($iterable, $options, true),
$options
);
}
public function current()
{
$current = $this->parserIterator->current();
if ($current instanceof Parser) {
return new self($current, $this->options);
}
return $current;
}
public function next()
{
$this->parserIterator->next();
}
public function key()
{
return $this->parserIterator->key();
}
public function valid(): bool
{
return $this->parserIterator->valid();
}
public function rewind()
{
$this->parserIterator = $this->parser->getIterator();
$this->parserIterator->rewind();
}
public function hasChildren(): bool
{
return $this->current() instanceof self;
}
public function getChildren()
{
$current = $this->current();
if ($current instanceof self) {
return $current;
}
return null;
}
}

View File

@ -9,7 +9,6 @@ use JsonMachine\JsonDecoder\PassThruDecoder;
/**
* @covers \JsonMachine\Items
* @covers \JsonMachine\RecursiveItems
*/
class ItemsTest extends \PHPUnit_Framework_TestCase
{

View File

@ -46,21 +46,6 @@ class NestedIteratorTest extends TestCase
$this->assertSame([false, true, false], $result);
}
public function testGetChildrenReturnsNestedIterator()
{
$generator = function () {yield from [1, new \ArrayIterator([]), 3]; };
$iterator = new NestedIterator($generator());
$result = [];
foreach ($iterator as $item) {
$result[] = $iterator->getChildren();
}
$this->assertSame(null, $result[0]);
$this->assertInstanceOf(NestedIterator::class, $result[1]);
$this->assertSame(null, $result[2]);
}
public function testGetChildrenReturnsCorrectItems()
{
$generator = function () {yield from [1, new \ArrayIterator([2]), 3]; };

View File

@ -590,13 +590,4 @@ class ParserTest extends \PHPUnit_Framework_TestCase
$this->expectExceptionMessage('generator');
iterator_to_array($array[1]);
}
public function testRecursiveIterationYieldsNestedIterator()
{
$iterator = new Parser(new Tokens(['[[1]]']), '', null, true);
foreach ($iterator as $item) {
$this->assertInstanceOf(NestedIterator::class, $item);
}
}
}

View File

@ -0,0 +1 @@
{"path": {"key":["value"]}}

View File

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace JsonMachineTest;
use JsonMachine\RecursiveItems;
/**
* @covers \JsonMachine\RecursiveItems
*/
class RecursiveItemsTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider data_testFactories
*/
public function testFactories($expected, $methodName, ...$args)
{
$iterator = call_user_func_array(RecursiveItems::class."::$methodName", [
$args[0],
[
'pointer' => $args[1],
'decoder' => $args[2],
'debug' => $args[3],
],
]);
$this->assertSame($expected, iterator_to_array($iterator));
}
public function data_testFactories()
{
foreach ([true, false] as $debug) {
foreach ([
[RecursiveItems::class, 'fromStream', fopen('data://text/plain,{"path": {"key":["value"]}}', 'r'), '/path', null, $debug],
[RecursiveItems::class, 'fromString', '{"path": {"key":["value"]}}', '/path', null, $debug],
[RecursiveItems::class, 'fromFile', __DIR__.'/RecursiveItemsTest.json', '/path', null, $debug],
[RecursiveItems::class, 'fromIterable', ['{"path": {"key', '":["value"]}}'], '/path', null, $debug],
[RecursiveItems::class, 'fromIterable', new \ArrayIterator(['{"path": {"key', '":["value"]}}']), '/path', null, $debug],
] as $case) {
yield $case;
}
}
}
public function testRecursiveIteration()
{
$items = RecursiveItems::fromString('[[":)"]]');
foreach ($items as $emojis) {
$this->assertInstanceOf(RecursiveItems::class, $emojis);
foreach ($emojis as $emoji) {
$this->assertSame(':)', $emoji);
}
}
}
public function testGetChildrenReturnsNestedIterator()
{
$iterator = RecursiveItems::fromString("[1,[],1]");
$result = [];
foreach ($iterator as $item) {
$result[] = $iterator->getChildren();
}
$this->assertSame(null, $result[0]);
$this->assertInstanceOf(RecursiveItems::class, $result[1]);
$this->assertSame(null, $result[2]);
}
public function testCurrentReturnsSameInstanceOfParser()
{
}
}