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

Merge branch 'master' into recursive

This commit is contained in:
Filip Halaxa 2023-11-29 23:16:33 +01:00
commit 27ba49f128
22 changed files with 174 additions and 36 deletions

View File

@ -13,6 +13,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
<br>
## 1.1.4 - 2023-11-28
- Minor fixes and added some tests.
### Added
- Support for PHP 8.3
- Added PHPStan to build pipeline
### Fixed
- Fixed the case when non-intersecting pointers were considered intersecting (#106). Thanks @XedinUnknown
<br>
## 1.1.3 - 2022-10-12
### Fixed
- Fix the parsing of nested sub-trees that use wildcards (#83). Thanks @cerbero90

View File

@ -12,7 +12,8 @@ define PHP_VERSIONS
"7.4 3.1.1"\
"8.0 3.1.1"\
"8.1 3.1.1"\
"8.2-rc 3.2.0alpha3"
"8.2 3.2.0"\
"8.3-rc 3.3.0alpha3"
endef
define DOCKER_RUN
@ -29,16 +30,15 @@ help:
@grep -E '^[-a-zA-Z0-9_\.\/]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[32m%-15s\033[0m\t%s\n", $$1, $$2}'
build: tests-all cs-check ## Run all necessary stuff before commit.
build: composer-validate cs-check phpstan tests-all ## Run all necessary stuff before commit.
tests: CMD=composer tests -- $(ARGS)
tests: docker-run ## Run tests on recent PHP version. Pass args to phpunit via ARGS=""
tests: ## Run tests on recent PHP version. Pass args to phpunit via ARGS=""
@$(call DOCKER_RUN,$(COVERAGE_PHP),composer tests -- $(ARGS))
tests-coverage: CMD=composer tests-coverage -- $(ARGS)
tests-coverage: ## Runs tests and creates ./clover.xml. Pass args to phpunit via ARGS=""
@$(call DOCKER_RUN,$(COVERAGE_PHP),$(CMD))
@$(call DOCKER_RUN,$(COVERAGE_PHP),composer tests-coverage -- $(ARGS))
tests-all: ## Run tests on all supported PHP versions. Pass args to phpunit via ARGS=""
@ -51,16 +51,24 @@ tests-all: ## Run tests on all supported PHP versions. Pass args to phpunit via
done
cs-check: CMD=composer cs-check
cs-check: docker-run ## Check code style
cs-check: ## Check code style
@$(call DOCKER_RUN,$(LATEST_PHP),composer cs-check)
cs-fix: CMD=composer cs-fix
cs-fix: docker-run ## Fix code style
phpstan: ## Run phpstan
@$(call DOCKER_RUN,$(LATEST_PHP),composer phpstan)
performance-tests: CMD=composer performance-tests
performance-tests: docker-run ## Run performance tests
cs-fix: ## Fix code style
@$(call DOCKER_RUN,$(LATEST_PHP),composer cs-fix)
performance-tests: ## Run performance tests
@$(call DOCKER_RUN,$(LATEST_PHP),composer performance-tests)
composer-validate: ## Validate composer.json contents
@$(call DOCKER_RUN,$(LATEST_PHP),composer validate)
release: .env build

View File

@ -1,9 +1,7 @@
<img align="right" src="img/github.png" />
(README in sync with the code)
# <img align="right" src="img/github.png" alt="JSON Machine" />
Very easy to use and memory efficient drop-in replacement for inefficient iteration of big JSON files or streams
for PHP >=7.0. See [TL;DR](#tl-dr). No dependencies in production except optional `ext-json`.
for PHP >=7.0. See [TL;DR](#tl-dr). No dependencies in production except optional `ext-json`. README in sync with the code
[![Build Status](https://github.com/halaxa/json-machine/actions/workflows/makefile.yml/badge.svg)](https://github.com/halaxa/json-machine/actions)
[![codecov](https://img.shields.io/codecov/c/gh/halaxa/json-machine?label=phpunit%20%40covers)](https://codecov.io/gh/halaxa/json-machine)

View File

@ -21,7 +21,12 @@ docker ps --all --format "{{.Names}}" | grep "$CONTAINER_NAME" && docker rm -f "
printf "
FROM $FROM_IMAGE
RUN apk add --update \
RUN apk update && apk upgrade
# https://stackoverflow.com/questions/76507083/pecl-install-no-releases-available#comment136513209_76651916
RUN rm /etc/ssl/certs/ca-cert-DST_Root_CA_X3.pem || true \
&& cat /etc/ssl/certs/*.pem > /etc/ssl/certs/ca-certificates.crt \
&& cat /etc/ssl/certs/*.pem > /etc/ssl/cert.pem
RUN apk add \
autoconf \
g++ \
libtool \
@ -31,6 +36,7 @@ printf "
&& wget http://pear.php.net/go-pear.phar && php go-pear.phar \
&& pecl install xdebug-$XDEBUG_VERSION \
&& docker-php-ext-enable xdebug \
&& docker-php-ext-enable opcache \
&& wget https://getcomposer.org/download/2.2.18/composer.phar -O /usr/local/bin/composer \
&& chmod +x /usr/local/bin/composer
" | docker build --quiet --tag "$CONTAINER_NAME" - > /dev/null

View File

@ -13,18 +13,20 @@
"tests-coverage": "build/composer-update.sh && XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-clover clover.xml",
"cs-check": "build/composer-update.sh && vendor/bin/php-cs-fixer fix --dry-run --verbose --allow-risky=yes",
"cs-fix": "build/composer-update.sh && vendor/bin/php-cs-fixer fix --verbose --allow-risky=yes",
"performance-tests": "php -n test/performance/testPerformance.php"
"phpstan": "build/composer-update.sh && vendor/bin/phpstan analyse",
"performance-tests": "php -d xdebug.mode=off -d opcache.enable_cli=1 -d opcache.jit_buffer_size=100M test/performance/testPerformance.php"
},
"config": {
"lock": false,
"sort-packages": true
},
"require": {
"php": ">=7.0"
"php": "7.0 - 8.3"
},
"require-dev": {
"ext-json": "*",
"friendsofphp/php-cs-fixer": "^3.0",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^8.0"
},
"suggest": {

21
phpstan.neon Normal file
View File

@ -0,0 +1,21 @@
parameters:
level: 5
paths:
- src
- test
bootstrapFiles:
- %rootDir%/../../../test/bootstrap.php
treatPhpDocTypesAsCertain: false
exceptions:
implicitThrows: false
check:
missingCheckedExceptionInThrows: true
tooWideThrowType: true
reportUncheckedExceptionDeadCatch: true
checkedExceptionClasses:
- JsonMachine\Exception\PathNotFoundException
- JsonMachine\Exception\SyntaxErrorException
- JsonMachine\Exception\UnexpectedEndSyntaxErrorException
uncheckedExceptionClasses:
- JsonMachine\Exception\InvalidArgumentException
- JsonMachine\Exception\JsonMachineException

View File

@ -6,7 +6,7 @@ namespace JsonMachine\Exception;
class SyntaxErrorException extends JsonMachineException
{
public function __construct($message, $position)
public function __construct(string $message, int $position)
{
parent::__construct($message." At position $position.");
}

View File

@ -4,6 +4,9 @@ declare(strict_types=1);
namespace JsonMachine;
/**
* @implements \IteratorAggregate<int, string>
*/
class FileChunks implements \IteratorAggregate
{
/** @var string */
@ -24,6 +27,8 @@ class FileChunks implements \IteratorAggregate
/**
* @return \Generator
*
* @throws Exception\InvalidArgumentException
*/
#[\ReturnTypeWillChange]
public function getIterator()

View File

@ -118,6 +118,8 @@ final class Items implements \IteratorAggregate, PositionAware
/**
* @return \Generator
*
* @throws Exception\PathNotFoundException
*/
#[\ReturnTypeWillChange]
public function getIterator()
@ -125,6 +127,9 @@ final class Items implements \IteratorAggregate, PositionAware
return $this->parser->getIterator();
}
/**
* @throws Exception\JsonMachineException
*/
public function getPosition()
{
return $this->parser->getPosition();
@ -135,11 +140,17 @@ final class Items implements \IteratorAggregate, PositionAware
return $this->parser->getJsonPointers();
}
/**
* @throws Exception\JsonMachineException
*/
public function getCurrentJsonPointer(): string
{
return $this->parser->getCurrentJsonPointer();
}
/**
* @throws Exception\JsonMachineException
*/
public function getMatchedJsonPointer(): string
{
return $this->parser->getMatchedJsonPointer();

View File

@ -12,6 +12,9 @@ class ItemsOptions extends \ArrayObject
{
private $options = [];
/**
* @throws InvalidArgumentException
*/
public function __construct(array $options = [])
{
$this->validateOptions($options);

View File

@ -44,13 +44,13 @@ class Parser implements \IteratorAggregate, PositionAware
/** @var ItemDecoder */
private $jsonDecoder;
/** @var string */
/** @var string|null */
private $matchedJsonPointer;
/** @var array */
private $paths;
/** @var array */
/** @var array|null */
private $currentPath;
/** @var array */
@ -276,7 +276,7 @@ class Parser implements \IteratorAggregate, PositionAware
}
if ($token === null) {
$this->error('Cannot iterate empty JSON', $token);
$this->error('Cannot iterate empty JSON', '');
}
if ($currentLevel > -1 && ! $subtreeEnded) {
@ -371,6 +371,9 @@ class Parser implements \IteratorAggregate, PositionAware
return array_values($this->jsonPointers);
}
/**
* @throws JsonMachineException
*/
public function getCurrentJsonPointer(): string
{
if ($this->currentPath === null) {
@ -380,6 +383,9 @@ class Parser implements \IteratorAggregate, PositionAware
return self::pathToJsonPointer($this->currentPath);
}
/**
* @throws JsonMachineException
*/
public function getMatchedJsonPointer(): string
{
if ($this->matchedJsonPointer === null) {

View File

@ -6,6 +6,9 @@ namespace JsonMachine;
use JsonMachine\Exception\InvalidArgumentException;
/**
* @implements \IteratorAggregate<int, string>
*/
class StreamChunks implements \IteratorAggregate
{
/** @var resource */
@ -17,6 +20,8 @@ class StreamChunks implements \IteratorAggregate
/**
* @param resource $stream
* @param int $chunkSize
*
* @throws InvalidArgumentException
*/
public function __construct($stream, $chunkSize = 1024 * 8)
{

View File

@ -4,17 +4,25 @@ declare(strict_types=1);
namespace JsonMachine;
/**
* @implements \IteratorAggregate<int, string>
*/
class TokensWithDebugging implements \IteratorAggregate, PositionAware
{
/** @var iterable */
/** @var iterable<int, string> */
private $jsonChunks;
/** @var int */
private $position = 0;
/** @var int */
private $line = 1;
/** @var int */
private $column = 0;
/**
* @param iterable<string> $jsonChunks
* @param iterable<int, string> $jsonChunks
*/
public function __construct($jsonChunks)
{
@ -53,6 +61,7 @@ class TokensWithDebugging implements \IteratorAggregate, PositionAware
foreach ($this->jsonChunks as $bytes) {
$bytesLength = strlen($bytes);
for ($i = 0; $i < $bytesLength; ++$i) {
/** @var string $byte */
$byte = $bytes[$i];
if ($inString) {
if ($byte == '"' && ! $escaping) {

View File

@ -8,16 +8,23 @@ use JsonMachine\Exception\InvalidArgumentException;
final class ValidJsonPointers
{
/** @var string[] */
private $jsonPointers = [];
/** @var bool */
private $validated = false;
/**
* @param string[] $jsonPointers
*/
public function __construct(array $jsonPointers)
{
$this->jsonPointers = array_values($jsonPointers);
}
/**
* @return string[]
*
* @throws InvalidArgumentException
*/
public function toArray(): array
@ -30,6 +37,8 @@ final class ValidJsonPointers
}
/**
* @return void
*
* @throws InvalidArgumentException
*/
private function validate()
@ -40,6 +49,8 @@ final class ValidJsonPointers
}
/**
* @return void
*
* @throws InvalidArgumentException
*/
private function validateFormat()
@ -54,6 +65,8 @@ final class ValidJsonPointers
}
/**
* @return void
*
* @throws InvalidArgumentException
*/
private function validateJsonPointersDoNotIntersect()
@ -64,8 +77,8 @@ final class ValidJsonPointers
continue;
}
if ($jsonPointerA === $jsonPointerB
|| self::str_contains($jsonPointerA, $jsonPointerB)
|| self::str_contains($jsonPointerA, self::wildcardify($jsonPointerB))
|| self::str_contains("$jsonPointerA/", "$jsonPointerB/")
|| self::str_contains("$jsonPointerA/", self::wildcardify("$jsonPointerB/"))
) {
throw new InvalidArgumentException(
sprintf(

View File

@ -19,7 +19,10 @@ namespace JsonMachine;
*/
class Autoloading
{
static public function autoloader($class)
/**
* @return void
*/
static public function autoloader(string $class)
{
$prefix = 'JsonMachine\\';
$baseDir = __DIR__.DIRECTORY_SEPARATOR;

View File

@ -14,7 +14,7 @@ class SyntaxErrorExceptionTest extends TestCase
{
public function testMessageContainsDataFromConstructor()
{
$exception = new SyntaxErrorException('msg 42', '24');
$exception = new SyntaxErrorException('msg 42', 24);
$this->assertContains('msg 42', $exception->getMessage());
$this->assertContains('24', $exception->getMessage());

View File

@ -141,6 +141,13 @@ class ItemsTest extends \PHPUnit_Framework_TestCase
$this->assertSame(['/one', '/two'], $items->getJsonPointers());
}
public function testCountViaIteratorCount()
{
$items = Items::fromIterable(['{"results":', '[1,2,3]}'], ['pointer' => ['/results']]);
$this->assertSame(3, iterator_count($items));
}
public function testRecursiveIteration()
{
$items = Items::fromString('[[":)"]]', ['recursive' => true]);

View File

@ -14,7 +14,7 @@ class DecodingErrorTest extends TestCase
{
public function testGetMalformedJson()
{
$decodingError = new DecodingError('"json\"', null);
$decodingError = new DecodingError('"json\"', '');
$this->assertSame('"json\"', $decodingError->getMalformedJson());
}

View File

@ -49,6 +49,11 @@ class ParserTest extends \PHPUnit_Framework_TestCase
['', '[{"c":1},"string",{"d":2},false]', [[0 => ['c' => 1]], [1 => 'string'], [2 => ['d' => 2]], [3 => false]]],
['', '[false,{"c":1},"string",{"d":2}]', [[0 => false], [1 => ['c' => 1]], [2 => 'string'], [3 => ['d' => 2]]]],
['', '[{"c":1,"d":2}]', [[['c' => 1, 'd' => 2]]]],
'ISSUE-108' => [
'',
'["https://click.justwatch.com/a?cx=eyJzY2hlbWEiOiJpZ2x1OmNvbS5zbm93cGxvd2FuYWx5dGljcy5zbm93cGxvdy9jb250ZXh0cy9qc29uc2NoZW1hLzEtMC0wIiwiZGF0YSI6W3sic2NoZW1hIjoiaWdsdTpjb20uanVzdHdhdGNoL2NsaWNrb3V0X2NvbnRleHQvanNvbnNjaGVtYS8xLTItMCIsImRhdGEiOnsicHJvdmlkZXIiOiJBcHBsZSBUViIsIm1vbmV0aXphdGlvblR5cGUiOiJidXkiLCJwcmVzZW50YXRpb25UeXBlIjoiaGQiLCJjdXJyZW5jeSI6IlVTRCIsInByaWNlIjo1MTkuNzQsIm9yaWdpbmFsUHJpY2UiOjAsImF1ZGlvTGFuZ3VhZ2UiOiIiLCJzdWJ0aXRsZUxhbmd1YWdlIjoiIiwiY2luZW1hSWQiOjAsInNob3d0aW1lIjoiIiwiaXNGYXZvcml0ZUNpbmVtYSI6ZmFsc2UsInBhcnRuZXJJZCI6MTI3MCwicHJvdmlkZXJJZCI6MiwiY2xpY2tvdXRUeXBlIjoianctY29udGVudC1wYXJ0bmVyLWFwaSJ9fSx7InNjaGVtYSI6ImlnbHU6Y29tLmp1c3R3YXRjaC90aXRsZV9jb250ZXh0L2pzb25zY2hlbWEvMS0wLTAiLCJkYXRhIjp7InRpdGxlSWQiOjIwOTgxLCJvYmplY3RUeXBlIjoic2hvdyIsImp3RW50aXR5SWQiOiJ0czIwOTgxIn19XX0\u0026r=https%3A%2F%2Ftv.apple.com%2Fus%2Fshow%2Fsurvivor%2Fumc.cmc.6ozd0mt09a86bpa19l885jv4z\u0026uct_country=us"]',
[['https://click.justwatch.com/a?cx=eyJzY2hlbWEiOiJpZ2x1OmNvbS5zbm93cGxvd2FuYWx5dGljcy5zbm93cGxvdy9jb250ZXh0cy9qc29uc2NoZW1hLzEtMC0wIiwiZGF0YSI6W3sic2NoZW1hIjoiaWdsdTpjb20uanVzdHdhdGNoL2NsaWNrb3V0X2NvbnRleHQvanNvbnNjaGVtYS8xLTItMCIsImRhdGEiOnsicHJvdmlkZXIiOiJBcHBsZSBUViIsIm1vbmV0aXphdGlvblR5cGUiOiJidXkiLCJwcmVzZW50YXRpb25UeXBlIjoiaGQiLCJjdXJyZW5jeSI6IlVTRCIsInByaWNlIjo1MTkuNzQsIm9yaWdpbmFsUHJpY2UiOjAsImF1ZGlvTGFuZ3VhZ2UiOiIiLCJzdWJ0aXRsZUxhbmd1YWdlIjoiIiwiY2luZW1hSWQiOjAsInNob3d0aW1lIjoiIiwiaXNGYXZvcml0ZUNpbmVtYSI6ZmFsc2UsInBhcnRuZXJJZCI6MTI3MCwicHJvdmlkZXJJZCI6MiwiY2xpY2tvdXRUeXBlIjoianctY29udGVudC1wYXJ0bmVyLWFwaSJ9fSx7InNjaGVtYSI6ImlnbHU6Y29tLmp1c3R3YXRjaC90aXRsZV9jb250ZXh0L2pzb25zY2hlbWEvMS0wLTAiLCJkYXRhIjp7InRpdGxlSWQiOjIwOTgxLCJvYmplY3RUeXBlIjoic2hvdyIsImp3RW50aXR5SWQiOiJ0czIwOTgxIn19XX0&r=https%3A%2F%2Ftv.apple.com%2Fus%2Fshow%2Fsurvivor%2Fumc.cmc.6ozd0mt09a86bpa19l885jv4z&uct_country=us']],
],
['/', '{"":{"c":1,"d":2}}', [['c' => 1], ['d' => 2]]],
['/~0', '{"~":{"c":1,"d":2}}', [['c' => 1], ['d' => 2]]],
['/~1', '{"/":{"c":1,"d":2}}', [['c' => 1], ['d' => 2]]],

View File

@ -15,6 +15,7 @@ class StreamChunksTest extends \PHPUnit_Framework_TestCase
public function testThrowsIfNoResource()
{
$this->expectException(InvalidArgumentException::class);
/* @phpstan-ignore-next-line */
new StreamChunks(false);
}

View File

@ -16,7 +16,6 @@ class ValidJsonPointersTest extends \PHPUnit_Framework_TestCase
* @dataProvider data_testThrowsOnIntersectingPaths
*
* @param $jsonPointers
* @param ParserTest $parserTest
*/
public function testThrowsOnIntersectingPaths($jsonPointers)
{
@ -70,11 +69,23 @@ class ValidJsonPointersTest extends \PHPUnit_Framework_TestCase
(new ValidJsonPointers(['/one', '/one']))->toArray();
}
public function testToArrayReturnsJsonPointers()
/**
* @dataProvider data_testToArrayReturnsJsonPointers
*/
public function testToArrayReturnsJsonPointers(string $pointerA, string $pointerB)
{
$this->assertSame(
['/one', '/two'],
(new ValidJsonPointers(['/one', '/two']))->toArray()
[$pointerA, $pointerB],
(new ValidJsonPointers([$pointerA, $pointerB]))->toArray()
);
}
public function data_testToArrayReturnsJsonPointers()
{
return [
['/one', '/two'],
['/companies/-/id', '/companies/0/idempotency_key'],
['/companies/1/id', '/companies/1/idempotency_key'],
];
}
}

View File

@ -6,8 +6,22 @@ use JsonMachine\Items;
require_once __DIR__.'/../../vendor/autoload.php';
if (in_array('xdebug', get_loaded_extensions())) {
trigger_error('Xdebug enabled. Results may be affected.', E_USER_WARNING);
if ( ! ini_get('xdebug.mode')) {
echo "Xdebug disabled\n";
} else {
echo "Xdebug enabled\n";
}
if ( ! function_exists('opcache_get_status')) {
echo "Opcache disabled\n";
echo "JIT disabled\n";
} else {
echo "Opcache enabled\n";
if (opcache_get_status()['jit']['enabled']) {
echo "JIT enabled\n";
} else {
echo "JIT disabled\n";
}
}
ini_set('memory_limit', '-1'); // for json_decode use case