Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 3.0 #27

Merged
merged 9 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:

- uses: shivammathur/setup-php@v2
with:
php-version: 7.1
php-version: 8.0
extensions: curl, mbstring
coverage: none
tools: composer:v2, cs2pr
Expand All @@ -29,19 +29,15 @@ jobs:
continue-on-error: ${{ !matrix.stable }}
strategy:
matrix:
php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
php: ['8.0', 8.1', '8.2', '8.3']
stable: [true]
coverage: [true]
composer-flags: ['']
include:
- php: '7.1'
- php: '8.0'
stable: true
coverage: false
composer-flags: '--prefer-lowest'
- php: '8.3'
stable: false
coverage: false
composer-flags: '--ignore-platform-req=php+'

steps:
- uses: actions/checkout@v2
Expand Down Expand Up @@ -77,7 +73,7 @@ jobs:

- uses: shivammathur/setup-php@v2
with:
php-version: 7.1
php-version: 8.0
extensions: curl, mbstring
coverage: none
tools: composer:v2
Expand Down
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip

## [Unreleased][unreleased]

## [3.0.0] - 2024-02-09

You should not notice any breaking changes in this release unless you were using named parameters, or ignoring argument types defined in docblocks.

### Changed
- Renamed function parameters to match `json_decode()`'s signature
- `$source` is now `$json`
- `$options` is now `$flags`
- Added explicit `mixed` return type to match `json_decode()`
- Added proper types to all parameters and return values of `SyntaxError`
- Renamed two arguments in the `SyntaxError`'s constructor:
- `$linenumber` is now `$lineNumber`
- `$columnNumber` is now `$column`

### Removed
- Removed support for PHP 7.x (8.0+ is now required)

## [2.3.0] - 2022-12-27

### Added
Expand Down Expand Up @@ -76,7 +93,8 @@ This release contains massive performance improvements of 98% or more, especiall
### Added
- Initial commit

[unreleased]: https://github.com/colinodell/json5/compare/v2.3.0...HEAD
[unreleased]: https://github.com/colinodell/json5/compare/v3.0.0...HEAD
[3.0.0]: https://github.com/colinodell/json5/compare/v2.3.0...v3.0.0
[2.3.0]: https://github.com/colinodell/json5/compare/v2.2.2...v2.3.0
[2.2.2]: https://github.com/colinodell/json5/compare/v2.2.1...v2.2.2
[2.2.1]: https://github.com/colinodell/json5/compare/v2.2.0...v2.2.1
Expand Down
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# JSON5 for PHP - JSON for Humans

[![Latest Version on Packagist][ico-version]][link-packagist]
[![PHP 7.1+][ico-php]][link-packagist]
[![PHP 8.0+][ico-php]][link-packagist]
[![Software License][ico-license]](LICENSE.md)
[![Build Status][ico-build-status]][link-build-status]
[![Coverage Status][ico-scrutinizer]][link-scrutinizer]
Expand Down Expand Up @@ -70,8 +70,6 @@ To achieve the best possible performance, it'll try parsing with PHP's native fu
This function will **always** throw a `SyntaxError` exception if parsing fails. This is a subclass of the new `\JsonException` introduced in PHP 7.3.
Providing or omitting the `JSON_THROW_ON_ERROR` option will have no effect on this behavior.

For users on PHP 7.2 and below: a PHP 7.3 polyfill is included with this library so that you can safely reference `\JsonException` and `JSON_THROW_ON_ERROR` in your own code.

## Binary / Executable

A binary/executable named `json5` is also provided for converting JSON5 to plain JSON via your terminal.
Expand Down
19 changes: 8 additions & 11 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,17 @@
}
],
"require": {
"php": "^7.1.3|^8.0",
"php": "^8.0",
"ext-json": "*",
"ext-mbstring": "*"
},
"require-dev": {
"mikehaertl/php-shellcommand": "^1.2.5",
"phpstan/phpstan": "^1.4",
"scrutinizer/ocular": "^1.6",
"squizlabs/php_codesniffer": "^2.3 || ^3.0",
"symfony/finder": "^4.4|^5.4|^6.0",
"symfony/phpunit-bridge": "^5.4|^6.0"
},
"conflict": {
"scrutinizer/ocular": "1.7.*"
"mikehaertl/php-shellcommand": "^1.7.0",
"phpstan/phpstan": "^1.10.57",
"scrutinizer/ocular": "^1.9",
"squizlabs/php_codesniffer": "^3.8.1",
"symfony/finder": "^6.0|^7.0",
"symfony/phpunit-bridge": "^7.0.3"
},
"autoload": {
"psr-4": {
Expand All @@ -59,7 +56,7 @@
},
"extra": {
"branch-alias": {
"dev-main": "3.0-dev"
"dev-main": "4.0-dev"
}
},
"config": {
Expand Down
142 changes: 52 additions & 90 deletions src/Json5Decoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,22 @@

final class Json5Decoder
{
private $json;

private $length;

private $at = 0;

private $currentByte;

private $lineNumber = 1;

private $associative;

private $maxDepth;

private $castBigIntToString;

private $depth = 1;

private $currentLineStartsAt = 0;
private int $length;
private int $at = 0;
private ?string $currentByte;
private int $lineNumber = 1;
private int $depth = 1;
private int $currentLineStartsAt = 0;

/**
* Private constructor.
*/
private function __construct(string $json, bool $associative = false, int $depth = 512, bool $castBigIntToString = false)
{
$this->json = $json;
$this->associative = $associative;
$this->maxDepth = $depth;
$this->castBigIntToString = $castBigIntToString;

private function __construct(
private string $json,
private bool $associative = false,
private int $maxDepth = 512,
private bool $castBigIntToString = false
) {
$this->length = \strlen($json);
$this->currentByte = $this->getByte(0);
}
Expand All @@ -56,35 +42,30 @@ private function __construct(string $json, bool $associative = false, int $depth
* The parameters exactly match PHP's json_decode() function - see
* http://php.net/manual/en/function.json-decode.php for more information.
*
* @param string $source The JSON string being decoded.
* @param string $json The JSON string being decoded.
* @param bool $associative When TRUE, returned objects will be converted into associative arrays.
* @param int $depth User specified recursion depth.
* @param int $options Bitmask of JSON decode options.
* @param int $flags Bitmask of JSON decode options.
*
* @throws SyntaxError if the JSON encoded string could not be parsed.
*
* @return mixed
*/
public static function decode(string $source, ?bool $associative = false, int $depth = 512, int $options = 0)
public static function decode(string $json, ?bool $associative = false, int $depth = 512, int $flags = 0): mixed
{
// Try parsing with json_decode first, since that's much faster
// We only attempt this on PHP 7+ because 5.x doesn't parse some edge cases correctly
if (PHP_VERSION_ID >= 70000) {
try {
$result = \json_decode($source, $associative, $depth, $options);
if (\json_last_error() === \JSON_ERROR_NONE) {
return $result;
}
} catch (\Throwable $e) {
// ignore exception, continue parsing as JSON5
try {
$result = \json_decode($json, $associative, $depth, $flags);
if (\json_last_error() === \JSON_ERROR_NONE) {
return $result;
}
} catch (\Throwable $e) {
// ignore exception, continue parsing as JSON5
}

// Fall back to JSON5 if that fails
$associative = $associative === true || ($associative === null && $options & \JSON_OBJECT_AS_ARRAY);
$castBigIntToString = $options & \JSON_BIGINT_AS_STRING;
$associative = $associative === true || ($associative === null && $flags & \JSON_OBJECT_AS_ARRAY);
$castBigIntToString = $flags & \JSON_BIGINT_AS_STRING;

$decoder = new self($source, $associative, $depth, $castBigIntToString);
$decoder = new self($json, $associative, $depth, $castBigIntToString);

$result = $decoder->value();
$decoder->white();
Expand Down Expand Up @@ -116,7 +97,7 @@ private function currentChar(): ?string
/**
* Parse the next character.
*/
private function next(): ?string
private function next(): void
{
// Get the next character. When there are no more characters,
// return the empty string.
Expand All @@ -127,13 +108,13 @@ private function next(): ?string

$this->at++;

return $this->currentByte = $this->getByte($this->at);
$this->currentByte = $this->getByte($this->at);
}

/**
* Parse the next character if it matches $c or fail.
*/
private function nextOrFail(string $c): ?string
private function nextOrFail(string $c): void
{
if ($c !== $this->currentByte) {
$this->throwSyntaxError(\sprintf(
Expand All @@ -143,7 +124,7 @@ private function nextOrFail(string $c): ?string
));
}

return $this->next();
$this->next();
}

/**
Expand Down Expand Up @@ -207,10 +188,7 @@ private function identifier(): string
return $unescaped;
}

/**
* @return int|float|string
*/
private function number()
private function number(): int|float|string
{
$number = null;
$sign = '';
Expand Down Expand Up @@ -426,10 +404,8 @@ private function white(): void

/**
* Matches true, false, null, etc
*
* @return bool|null|float
*/
private function word()
private function word(): bool|float|null
{
switch ($this->currentByte) {
case 't':
Expand Down Expand Up @@ -512,10 +488,8 @@ private function arr(): array

/**
* Parse an object value
*
* @return array|object
*/
private function obj()
private function obj(): array|object
{
$object = $this->associative ? [] : new \stdClass;

Expand Down Expand Up @@ -567,27 +541,17 @@ private function obj()
*
* It could be an object, an array, a string, a number,
* or a word.
*
* @return mixed
*/
private function value()
private function value(): mixed
{
$this->white();
switch ($this->currentByte) {
case '{':
return $this->obj();
case '[':
return $this->arr();
case '"':
case "'":
return $this->string();
case '-':
case '+':
case '.':
return $this->number();
default:
return \is_numeric($this->currentByte) ? $this->number() : $this->word();
}
return match ($this->currentByte) {
'{' => $this->obj(),
'[' => $this->arr(),
'"', "'" => $this->string(),
'-', '+', '.' => $this->number(),
default => \is_numeric($this->currentByte) ? $this->number() : $this->word(),
};
}

/**
Expand All @@ -611,20 +575,18 @@ private static function renderChar(?string $chr): string

private static function getEscapee(string $ch): ?string
{
switch ($ch) {
// @codingStandardsIgnoreStart
case "'": return "'";
case '"': return '"';
case '\\': return '\\';
case '/': return '/';
case "\n": return '';
case 'b': return \chr(8);
case 'f': return "\f";
case 'n': return "\n";
case 'r': return "\r";
case 't': return "\t";
default: return null;
// @codingStandardsIgnoreEnd
}
return match ($ch) {
"'" => "'",
'"' => '"',
'\\' => '\\',
'/' => '/',
"\n" => '',
'b' => \chr(8),
'f' => "\f",
'n' => "\n",
'r' => "\r",
't' => "\t",
default => null,
};
}
}
Loading
Loading