Skip to content

3.0.0: Throw errors/SimdJsonException in more cases to be consistent with json_decode, make C bindings usable by other PECLs #86

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

Merged
merged 9 commits into from
Oct 17, 2022
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
69 changes: 56 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,47 +108,75 @@ var_dump($res) //int(5)
<?php

/**
* Takes a JSON encoded string and converts it into a PHP variable.
* Similar to json_decode()
*
* @param string $json The JSON string being decoded
* @param bool $associative When true, JSON objects will be returned as associative arrays.
* When false, JSON objects will be returned as objects.
* @param int $depth the maximum nesting depth of the structure being decoded.
* @return array|stdClass|string|float|int|bool|null
* @throws SimdJsonException for invalid JSON
* (or document over 4GB, or out of range integer/float)
* (or $json over 4GB long, or out of range integer/float)
* @throws SimdJsonValueError for invalid $depth
*/
function simdjson_decode(string $json, bool $assoc = false, int $depth = 512) {}
function simdjson_decode(string $json, bool $associative = false, int $depth = 512) {}

/**
* Returns true if json is valid.
*
* @return ?bool (null if depth is invalid)
* @param string $json The JSON string being decoded
* @param int $depth the maximum nesting depth of the structure being decoded.
* @return bool
* @throws SimdJsonValueError for invalid $depth
*/
function simdjson_is_valid(string $json, int $depth = 512) : ?bool {}
function simdjson_is_valid(string $json, int $depth = 512) : bool {}

/**
* Parses $json and returns the number of keys in $json matching the JSON pointer $key
*
* @return ?int (null if depth is invalid)
* @throws SimdJsonException for invalid JSON
* @param string $json The JSON string being decoded
* @param string $key The JSON pointer being requested
* @param int $depth The maximum nesting depth of the structure being decoded.
* @param bool $throw_if_uncountable If true, then throw SimdJsonException instead of returning 0 for JSON pointers
to values that are neither objects nor arrays.
* @return int
* @throws SimdJsonException for invalid JSON or invalid JSON pointer
* (or document over 4GB, or out of range integer/float)
* @throws SimdJsonValueError for invalid $depth
* @see https://www.rfc-editor.org/rfc/rfc6901.html
*/
function simdjson_key_count(string $json, string $key, int $depth = 512) : ?int {}
function simdjson_key_count(string $json, string $key, int $depth = 512, bool $throw_if_uncountable = false) : int {}

/**
* Returns true if the JSON pointer $key could be found.
*
* @return ?bool (null if depth is invalid, false if json is invalid or key is not found)
* @throws SimdJsonException for invalid JSON
* @param string $json The JSON string being decoded
* @param string $key The JSON pointer being requested
* @param int $depth the maximum nesting depth of the structure being decoded.
* @return bool (false if key is not found)
* @throws SimdJsonException for invalid JSON or invalid JSON pointer
* (or document over 4GB, or out of range integer/float)
* @throws SimdJsonValueError for invalid $depth
* @see https://www.rfc-editor.org/rfc/rfc6901.html
*/
function simdjson_key_exists(string $json, string $key, int $depth = 512) : ?bool {}
function simdjson_key_exists(string $json, string $key, int $depth = 512) : bool {}

/**
* Returns the value at $key
* Returns the value at the json pointer $key
*
* @param string $json The JSON string being decoded
* @param string $key The JSON pointer being requested
* @param int $depth the maximum nesting depth of the structure being decoded.
* @param bool $associative When true, JSON objects will be returned as associative arrays.
* When false, JSON objects will be returned as objects.
* @return array|stdClass|string|float|int|bool|null the value at $key
* @throws SimdJsonException for invalid JSON
* @throws SimdJsonException for invalid JSON or invalid JSON pointer
* (or document over 4GB, or out of range integer/float)
* @throws SimdJsonValueError for invalid $depth
* @see https://www.rfc-editor.org/rfc/rfc6901.html
*/
function simdjson_key_value(string $json, string $key, bool $assoc = unknown, int $depth = unknown) {}
function simdjson_key_value(string $json, string $key, bool $associative = false, int $depth = 512) {}

/**
* An error thrown by simdjson when processing json.
Expand All @@ -160,12 +188,27 @@ function simdjson_key_value(string $json, string $key, bool $assoc = unknown, in
*/
class SimdJsonException extends RuntimeException {
}

/**
* Thrown for error conditions on fields such as $depth that are not expected to be
* from user-provided JSON, with similar behavior to php 8.0.
*
* NOTE: https://www.php.net/valueerror was added in php 8.0.
* In older php versions, this extends Error instead.
*
* When support for php 8.0 is dropped completely,
* a major release of simdjson will likely switch to a standard ValueError.
*/
class SimdJsonValueError extends ValueError {
}
```

## Edge cases

There are some differences from `json_decode()` due to the implementation of the underlying simdjson library. This will throw a RuntimeException if simdjson rejects the JSON.

Note that the simdjson PECL is using a fork of the simdjson C library to imitate php's handling of integers and floats in JSON.

1) **Until simdjson 2.1.0,** `simdjson_decode()` differed in how out of range 64-bit integers and floats are handled.

See https://github.com/simdjson/simdjson/blob/master/doc/basics.md#standard-compliance
Expand Down
4 changes: 2 additions & 2 deletions config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ if test "$PHP_SIMDJSON" != "no"; then
dnl Disable development checks of C simdjson library in php debug builds (can manually override)
PHP_NEW_EXTENSION(simdjson, [
php_simdjson.cpp \
src/bindings.cpp \
src/simdjson_bindings.cpp \
src/simdjson.cpp],
$ext_shared,, "-std=c++17 -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 -DSIMDJSON_EXCEPTIONS=0 -DSIMDJSON_DEVELOPMENT_CHECKS=0", cxx)

PHP_INSTALL_HEADERS([ext/simdjson], [php_simdjson.h, src/bindings.h src/bindings_defs.h])
PHP_INSTALL_HEADERS([ext/simdjson], [php_simdjson.h src/simdjson_bindings_defs.h])
PHP_ADD_MAKEFILE_FRAGMENT
PHP_ADD_BUILD_DIR(src, 1)
fi
4 changes: 2 additions & 2 deletions config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ if (PHP_SIMDJSON == "yes") {
'php_simdjson.cpp',
'yes',
'/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 /std:c++latest');
ADD_SOURCES(configure_module_dirname + '/src', 'simdjson.cpp bindings.cpp', 'simdjson');
ADD_SOURCES(configure_module_dirname + '/src', 'simdjson.cpp simdjson_bindings.cpp', 'simdjson');
ADD_FLAG('CFLAGS_SIMDJSON', '/I' + configure_module_dirname);
PHP_INSTALL_HEADERS('ext/simdjson', 'php_simdjson.h src/bindings.h src/bindings_defs.h');
PHP_INSTALL_HEADERS('ext/simdjson', 'php_simdjson.h src/simdjson_bindings_defs.h');
}
// vim:ft=javascript
49 changes: 35 additions & 14 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@
-->
<date>2022-10-14</date>
<version>
<release>2.1.0</release>
<api>2.1.0</api>
<release>3.0.0dev</release>
<api>3.0.0dev</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="https://www.apache.org/licenses/LICENSE-2.0.html">Apache 2.0</license>
<notes>
* Allow out of range 64-bit values in JSON integer syntax and allow floating point values outside of the max/min finite floating point values (i.e. parsing to +/- infinity).

This allows simdjson_decode() to be used as a replacement for json_decode() in more use cases.
* Return the correct value in simdjson_key_count() for JSON pointers to arrays/objects exceeding size 0xFFFFFF.
Previously, this would be limited to returning at most 0xFFFFFF(16777215).
* Throw 'SimdJsonException extends RuntimeException' instead of RuntimeException.
* Set the error code from simdjson as SimdJsonException->getCode()
* Expose error_code constants from simdjson as `SIMDJSON_ERR_$ERRCODENAME`
* Add SimdJsonValueError. In php 8.0+, it extends ValueError, and it extends Error in older php versions.
This provides an API similar to the JSON module, which started throwing ValueError for invalid depths in php 8.0.
* Throw SimdJsonValueError instead of emitting notices if $depth is too small or too large in all simdjson PHP functions.
simdjson_is_valid(), simdjson_key_count() and simdjson_key_exists() now have non-null return types.
* Throw a SimdJsonException in simdjson_key_exists on error conditions such as invalid json, to be consistent with other simdjson PHP functions.
* Add an optional boolean `$throw_if_uncountable = false` to simdjson_key_count.
When this is overridden to be true, simdjson_key_count will throw a SimdJsonException if the JSON pointer refers to a value that exists but is neither an array nor an object instead of returning 0.
* Rename the parameter $assoc to $associative in simdjson_decode and simdjson_key_value, to match naming practices used in json_decode()
</notes>
<contents>
<dir name="/">
Expand All @@ -51,9 +51,8 @@
<file name="simdjson.stub.php" role="src"/>
<file name="simdjson_arginfo.h" role="src"/>
<dir name="src">
<file name="bindings.cpp" role="src"/>
<file name="bindings.h" role="src"/>
<file name="bindings_defs.h" role="src"/>
<file name="simdjson_bindings.cpp" role="src"/>
<file name="simdjson_bindings_defs.h" role="src"/>
<file name="simdjson.cpp" role="src"/>
<file name="simdjson.h" role="src"/>
</dir>
Expand All @@ -71,6 +70,7 @@
<file name="decode_strict_types.phpt" role="test"/>
<file name="decode_types.phpt" role="test"/>
<file name="depth.phpt" role="test"/>
<file name="dump.inc" role="test"/>
<file name="is_valid.phpt" role="test"/>
<file name="is_valid_args.phpt" role="test"/>
<file name="key_count.phpt" role="test"/>
Expand Down Expand Up @@ -101,7 +101,6 @@
<file name="bug69187.phpt" role="test"/>
<file name="fail001.phpt" role="test"/>
<file name="json_decode_basic.phpt" role="test"/>
<file name="json_decode_error.phpt" role="test"/>
<file name="json_decode_invalid_utf8.phpt" role="test"/>
<file name="pass001.1_64bit.phpt" role="test"/>
<file name="pass001.1.phpt" role="test"/>
Expand All @@ -125,6 +124,28 @@
<providesextension>simdjson</providesextension>
<extsrcrelease/>
<changelog>
<release>
<date>2022-10-14</date>
<version>
<release>2.1.0</release>
<api>2.1.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="https://www.apache.org/licenses/LICENSE-2.0.html">Apache 2.0</license>
<notes>
* Allow out of range 64-bit values in JSON integer syntax and allow floating point values outside of the max/min finite floating point values (i.e. parsing to +/- infinity).

This allows simdjson_decode() to be used as a replacement for json_decode() in more use cases.
* Return the correct value in simdjson_key_count() for JSON pointers to arrays/objects exceeding size 0xFFFFFF.
Previously, this would be limited to returning at most 0xFFFFFF(16777215).
* Throw 'SimdJsonException extends RuntimeException' instead of RuntimeException.
* Set the error code from simdjson as SimdJsonException->getCode()
* Expose error_code constants from simdjson as `SIMDJSON_ERR_$ERRCODENAME`
</notes>
</release>
<release>
<date>2022-10-01</date>
<version>
Expand Down
Loading