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:
parent
754d3609c0
commit
cf83311f16
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ use JsonMachine\JsonDecoder\PassThruDecoder;
|
||||
|
||||
/**
|
||||
* @covers \JsonMachine\Items
|
||||
* @covers \JsonMachine\RecursiveItems
|
||||
*/
|
||||
class ItemsTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
@ -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]; };
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1
test/JsonMachineTest/RecursiveItemsTest.json
Normal file
1
test/JsonMachineTest/RecursiveItemsTest.json
Normal file
@ -0,0 +1 @@
|
||||
{"path": {"key":["value"]}}
|
75
test/JsonMachineTest/RecursiveItemsTest.php
Normal file
75
test/JsonMachineTest/RecursiveItemsTest.php
Normal 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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user