10 Commits

Author SHA1 Message Date
jorgecc 4f2892bc19 2.5 2025-03-02 18:59:24 -03:00
jorgecc 0753c8c54a 2.4 2024-04-06 12:37:50 -03:00
jorgecc 7d957752be 2.4 2024-04-06 12:37:23 -03:00
jorgecc b1a90af079 10/11 2022-11-10 07:16:32 -03:00
jorgecc 43246435ec 2.3.2 2022-11-09 21:51:27 -03:00
jorgecc 269be53a1f 2.3.1 2022-11-08 07:53:28 -03:00
jorgecc a8dcaddec5 2.3 2022-11-07 19:47:01 -03:00
jorgecc 470badb3b0 2.0 2022-11-07 17:14:43 -03:00
jorgecc ede74bc719 2.0 2022-11-07 17:13:12 -03:00
jorgecc 82c2cad5f9 7/11 2022-11-07 11:42:04 -03:00
7 changed files with 451 additions and 360 deletions
+86 -39
View File
@@ -1,5 +1,5 @@
# Services_JSON # Services_JSON
PHP implementaion of json_encode/decode for PHP 7.1 and higher. PHP implementation of json_encode/decode for PHP 7.2 and higher. This library works with and without quoted keys.
[![Packagist](https://img.shields.io/packagist/v/eftec/services_json.svg)](https://packagist.org/packages/eftec/services_json) [![Packagist](https://img.shields.io/packagist/v/eftec/services_json.svg)](https://packagist.org/packages/eftec/services_json)
[![Total Downloads](https://poser.pugx.org/eftec/services_json/downloads)](https://packagist.org/packages/eftec/services_json) [![Total Downloads](https://poser.pugx.org/eftec/services_json/downloads)](https://packagist.org/packages/eftec/services_json)
@@ -9,69 +9,100 @@ PHP implementaion of json_encode/decode for PHP 7.1 and higher.
[![php](https://img.shields.io/badge/php-8.x-green.svg)]() [![php](https://img.shields.io/badge/php-8.x-green.svg)]()
[![CocoaPods](https://img.shields.io/badge/docs-70%25-yellow.svg)]() [![CocoaPods](https://img.shields.io/badge/docs-70%25-yellow.svg)]()
JSON (JavaScript Object Notation, http://json.org) is a lightweight data-interchange format. This package provides a simple encoder and decoder for JSON notation. It is intended for use with client-side Javascript
It is easy for humans to read and write. It is easy for machines to parse and generate. applications that make use of HTTPRequest to perform server communication functions - data can be encoded into JSON
It is based on a subset of the JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999. notation for use in a client-side javascript, or decoded from incoming Javascript requests. JSON format is native to
This feature can also be found in Python. JSON is a text format that is completely language independent Javascript, and can be directly eval() with no further parsing overhead.
but uses conventions that are familiar to programmers of the C-family of languages, including
C, C++, C#, Java, JavaScript, Perl, TCL, and many others. These properties make JSON an ideal
data-interchange language.
This package provides a simple encoder and decoder for JSON notation. It is intended for use
with client-side Javascript applications that make use of HTTPRequest to perform server
communication functions - data can be encoded into JSON notation for use in a client-side
javascript, or decoded from incoming Javascript requests. JSON format is native to Javascript,
and can be directly eval() with no further parsing overhead.
## So, what is the goal with this version? ## So, what is the goal with this version?
While this version doesn't have a better performance than json_encode() and json_decode() available as extension, While this version doesn't have a better performance than **json_encode()** and **json_decode()** available as
it has the next features: extension, but it has the next features:
[x] it doesn't require an extension. If you can't install an extension, then you can use this version. - [x] it doesn't require an extension. If you can't install ext-json, then you can use this version.
[x] **it works with json with unquoted keys** (for example javascript notation) - [x] **it works with JSON with unquoted keys** (for example JavaScript notation), {aaa:2,bbb:"hello"}
[x] It is a simple file with no dependency. - [x] **it could also work with unquoted values** {"aaa":2,"bbb":hello}
- [x] **it could also work with unwrapped content** "aaa":2,"bbb":"hello" instead of {aaa:2,bbb:"hello"}
- [x] It is a simple .php file with no dependency.
## Usage ## Usage
### Getting started ### Getting started using composer
* Install the library using composer (or you could download Services_JSON.php manually) 1. Install the library using composer
```shell ```shell
composer require eftec/services_json composer require eftec/services_json
``` ```
* Include the dependency 2. Include the dependency
```php ```php
use eftec\ServicesJson\Services_JSON; use eftec\ServicesJson\Services_JSON;
include '../vendor/autoload.php'; include '../vendor/autoload.php';
$s=new Services_JSON();
``` ```
* And creates a service class
```php
$s=new Services_JSON();
```
See the folder examples for further examples See the folder examples for further examples
### Getting started without composer
1. Copy this file: [Services_JSON/Services_JSON.php](https://github.com/EFTEC/Services_JSON/blob/main/src/Services_JSON.php)
2. Include the file
```php
use eftec\ServicesJson\Services_JSON;
include 'Services_JSON.php'; // or where is located the file.
```
### Decode ### Decode
Decode transform (decodified) a json string into a stdclass or an associative array Decode a JSON string into a stdclass or an associative array
```php ```php
$s=new Services_JSON();
$json='{"hello":{"a":2,"b":3},"world":[1,2,3,"aaa"]}'; $json='{"hello":{"a":2,"b":3},"world":[1,2,3,"aaa"]}';
var_dump($s->decode($json)); // as stdclass var_dump(Services_JSON::decode($json)); // as stdclass
var_dump($s->decode($json,true)); // as array var_dump(Services_JSON::decode($json,Services_JSON::GET_ARRAY)); // as array
``` ```
It also works with unquoted keys It also works with unquoted keys
```php ```php
$s=new Services_JSON();
$json='{hello:{a:2,b:3},world:[1,2,3,"aaa","bbbb"]}'; // the keys are unquoted. $json='{hello:{a:2,b:3},world:[1,2,3,"aaa","bbbb"]}'; // the keys are unquoted.
var_dump($s->decode($json)); // as stdclass var_dump(Services_JSON::decode($json)); // as stdclass
var_dump($s->decode($json,true)); // as array var_dump(Services_JSON::decode($json,Services_JSON::GET_ARRAY)); // as array
``` ```
#### Json unwrapped.
With the flag **Services_JSON::DECODE_FIX_ROOT**, it also works when the origin text misses [] and {} at the start of the code.
Note, this feature is not foolproof, for example "[1,2,3],[4,5,6]" will not wrap as "[[1,2,3],[4,5,6]]"
```
{"a":222,"b:"ccc"} # normal json
"a":222,"b:"ccc" # json without the root parenthesis.
```
```php
Services_JSON::decode('1,2,3',Services_JSON::GET_ARRAY | Services_JSON::DECODE_FIX_ROOT); // returns [1,2,3]
Services_JSON::decode('"k1":"v1", k2:2',Services_JSON::GET_ARRAY | Services_JSON::DECODE_FIX_ROOT) // returns [ 'k1' => 'v1','k2'=>2]
```
> Note: DECODE_FIX_ROOT flag detects if the near character is ":" or ",". If the closest character is ":", then it returns
> an object, otherwise it returns a list. If there is none, then it returns a list.
#### Json with unquoted values
With the flag **Services_JSON::DECODE_NO_QUOTE** it also works when the string values are not quoted.
>Note: invisible characters at the beginner and at the end (tabs, line carriages) will be trimmed.
```php
Services_JSON::decode('{"a":aaa,"b":bbbb,"c": aaaaa}'
, Services_JSON::GET_ARRAY | Services_JSON::DECODE_NO_QUOTE) // ['a'=>'aaa','b'=>'bbbb','c' => 'aaaaa']
```
### Encode ### Encode
Encode transform a value (array, object, primitive value, etc.) into a json expression (a string) Encode transform a value (array, object, primitive value, etc.) into a json expression (a string)
@@ -79,18 +110,34 @@ Encode transform a value (array, object, primitive value, etc.) into a json expr
```php ```php
$array=["hello"=>['a'=>2,'b'=>3],'world'=>[1,2,3,"aaa","bbb"]]; $array=["hello"=>['a'=>2,'b'=>3],'world'=>[1,2,3,"aaa","bbb"]];
$obj=(object)$array; $obj=(object)$array;
var_dump($s->encode($array)); // encode an associative array var_dump(Services_JSON::encode($array)); // encode an associative array
var_dump($s->encode($obj)); // encode an object var_dump(Services_JSON::encode($obj)); // encode an object
``` ```
## Changelog ## Changelog
* 2.5
* now allows "@" and "." for key values.
* 2.4
* update requirements to php 7.4
* added more type hinting (type validation)
* 2.3.2
* Added flag to decode() DECODE_NO_QUOTE
* 2.3.1
* deleted unused code
* fixed comments.
* 25% of the code has been refactored.
* 2.3
* Fixed a typo with a comment.
* added phpunit. The entire code is tested but special codification.
* 2.2
* Now the library is static, so you can call the methods without creating an instance.
* If you want to work with the non-static library, then install 1.1
* 1.1 * 1.1
* It works with PHP 7.2 and higher. * It works with PHP 7.2 and higher (including PHP 8.0 and 8.1)
* It doesn't require PECL to work. * It doesn't require PECL to work.
* The code was cleaned * The code was cleaned
* web header is removed. * Web header is removed.
* Method decode() could return an associative array. * Method decode() could return an associative array.
* 1.0.3-1.0.0 PECL version. It only works with PHP 4.x and PHP 5.x * 1.0.3-1.0.0 PECL version. It only works with PHP 4.x and PHP 5.x
+4 -1
View File
@@ -25,9 +25,12 @@
} }
], ],
"require": { "require": {
"php": ">=7.2.5" "php": ">=7.4"
}, },
"suggest": { "suggest": {
"ext-mbstring": "*" "ext-mbstring": "*"
},
"require-dev": {
"phpunit/phpunit": "^9.5"
} }
} }
+3 -4
View File
@@ -1,15 +1,14 @@
<?php <?php /** @noinspection ForgottenDebugOutputInspection */
use eftec\ServicesJson\Services_JSON; use eftec\ServicesJson\Services_JSON;
include '../vendor/autoload.php'; include '../vendor/autoload.php';
$s=new Services_JSON();
$json='{"hello":{"a":2,"b":3},"world":[1,2,3,"aaa"]}'; $json='{"hello":{"a":2,"b":3},"world":[1,2,3,"aaa"]}';
echo "Decode as stdclass:<br>"; echo "Decode as stdclass:<br>";
echo "<pre>"; echo "<pre>";
var_dump($s->decode($json)); var_dump(Services_JSON::decode($json));
echo "</pre>"; echo "</pre>";
echo "Decode as array:<br>"; echo "Decode as array:<br>";
echo "<pre>"; echo "<pre>";
var_dump($s->decode($json,true)); var_dump(Services_JSON::decode($json, Services_JSON::GET_ARRAY));
echo "</pre>"; echo "</pre>";
+24 -6
View File
@@ -1,17 +1,35 @@
<?php <?php /** @noinspection ForgottenDebugOutputInspection */
use eftec\ServicesJson\Services_JSON; use eftec\ServicesJson\Services_JSON;
include '../vendor/autoload.php'; include '../vendor/autoload.php';
$s=new Services_JSON(); // $json_decoder = new Services_JSON(SERVICES_JSON_IN_ARR | SERVICES_JSON_LOOSE_TYPE)
$json='{hello:{a:2,b:3},world:[1,2,3,"aaa","bbbb"]}'; $json='{hello:{a:2,b:3},world:[1,2,3,"aaa","bbbb"]}';
echo "Decode as stdclass:<br>";
echo "<h1>Original:</h1><pre>$json</pre><br>";
echo "<h1>Decode as stdclass:</h1>";
echo "<pre>"; echo "<pre>";
var_dump($s->decode($json)); var_dump(Services_JSON::decode($json));
echo "</pre>"; echo "</pre>";
echo "Decode as array:<br>"; echo "<h1>Decode as array:</h1>";
echo "<pre>"; echo "<pre>";
var_dump($s->decode($json,true)); var_dump(Services_JSON::decode($json,Services_JSON::GET_ARRAY));
echo "</pre>"; echo "</pre>";
$json='hello.world:{a:2,b:3},hello@world:[1,2,3,"aaa","bbbb"]';
echo "<h1>Original (.@ and missing {} or [] in root):</h1><pre>$json</pre><br>";
echo "<h1>Decode as stdclass:</h1>";
echo "<pre>";
var_dump(Services_JSON::decode($json,Services_JSON::DECODE_FIX_ROOT));
echo "</pre>";
echo "<h1>Decode as array:</h1>";
echo "<pre>";
var_dump(Services_JSON::decode($json,Services_JSON::GET_ARRAY|Services_JSON::DECODE_FIX_ROOT));
echo "</pre>";
+4 -4
View File
@@ -1,10 +1,10 @@
<?php <?php /** @noinspection ForgottenDebugOutputInspection */
use eftec\ServicesJson\Services_JSON; use eftec\ServicesJson\Services_JSON;
include '../vendor/autoload.php'; include '../vendor/autoload.php';
$s=new Services_JSON();
$array=["hello"=>['a'=>2,'b'=>3],'world'=>[1,2,3,"aaa","bbb"]]; $array=["hello"=>['a'=>2,'b'=>3],'world'=>[1,2,3,"aaa","bbb"]];
@@ -12,9 +12,9 @@ $obj=(object)$array;
echo "Encode array to json<br>"; echo "Encode array to json<br>";
echo "<pre>"; echo "<pre>";
var_dump($s->encode($array)); var_dump(Services_JSON::encode($array));
echo "</pre>"; echo "</pre>";
echo "Encode object to json<br>"; echo "Encode object to json<br>";
echo "<pre>"; echo "<pre>";
var_dump($s->encode($obj)); var_dump(Services_JSON::encode($obj));
echo "</pre>"; echo "</pre>";
+240 -306
View File
@@ -1,10 +1,12 @@
<?php /** @noinspection TypeUnsafeArraySearchInspection */ <?php /** @noinspection UnknownInspectionInspection */
/** @noinspection PhpComposerExtensionStubsInspection */
/** @noinspection TypeUnsafeComparisonInspection */ /**
* @noinspection TypeUnsafeArraySearchInspection
* @noinspection PhpComposerExtensionStubsInspection
* @noinspection TypeUnsafeComparisonInspection
*/
namespace eftec\ServicesJson; namespace eftec\ServicesJson;
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
use RuntimeException; use RuntimeException;
use stdClass; use stdClass;
@@ -12,16 +14,6 @@ use stdClass;
/** /**
* Converts to and from JSON format. * Converts to and from JSON format.
* *
* JSON (JavaScript Object Notation) is a lightweight data-interchange
* format. It is easy for humans to read and write. It is easy for machines
* to parse and generate. It is based on a subset of the JavaScript
* Programming Language, Standard ECMA-262 3rd Edition - December 1999.
* This feature can also be found in Python. JSON is a text format that is
* completely language independent but uses conventions that are familiar
* to programmers of the C-family of languages, including C, C++, C#, Java,
* JavaScript, Perl, TCL, and many others. These properties make JSON an
* ideal data-interchange language.
*
* This package provides a simple encoder and decoder for JSON notation. It * This package provides a simple encoder and decoder for JSON notation. It
* is intended for use with client-side Javascript applications that make * is intended for use with client-side Javascript applications that make
* use of HTTPRequest to perform server communication functions - data can * use of HTTPRequest to perform server communication functions - data can
@@ -59,44 +51,12 @@ use stdClass;
* @author Matt Knapp <mdknapp[at]gmail[dot]com> * @author Matt Knapp <mdknapp[at]gmail[dot]com>
* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
* @autor Jorge Castro <jcastro@eftec.cl> * @autor Jorge Castro <jcastro@eftec.cl>
* @copyright 2005 Michal Migurski * @copyright 2005 Michal Migurski,2022 Jorge Castro
* @version 1.1 2022-11-07 * @version 2.0 2022-11-07
* @license http://www.opensource.org/licenses/bsd-license.php * @license http://www.opensource.org/licenses/bsd-license.php
* @link https://https://github.com/EFTEC/Services_JSON * @link https://https://github.com/EFTEC/Services_JSON
* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
*/ */
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_SLICE', 1);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_STR', 2);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_ARR', 3);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_OBJ', 4);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_CMT', 5);
/**
* Behavior switch for Services_JSON::decode()
*/
define('SERVICES_JSON_LOOSE_TYPE', 16);
/**
* Behavior switch for Services_JSON::decode()
*/
define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
/**
* Behavior switch for Services_JSON::decode()
*/
define('SERVICES_JSON_USE_TO_JSON', 64);
/** /**
* Converts to and from JSON format. * Converts to and from JSON format.
@@ -104,56 +64,59 @@ define('SERVICES_JSON_USE_TO_JSON', 64);
* Brief example of use: * Brief example of use:
* *
* <code> * <code>
* // create a new instance of Services_JSON
* $json = new Services_JSON();
* *
* // convert a complexe value to JSON notation, and send it to the browser * // convert a complexe value to JSON notation, and send it to the browser
* $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
* $output = $json->encode($value); * $output = Services_JSON::encode($value);
* *
* print($output); * print($output);
* // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
* *
* // accept incoming POST data, assumed to be in JSON notation * // accept incoming POST data, assumed to be in JSON notation
* $input = file_get_contents('php://input', 1000000); * $input = file_get_contents('php://input', 1000000);
* $value = $json->decode($input); * $value = Services_JSON::decode($input);
* </code> * </code>
*/ */
class Services_JSON class Services_JSON
{ {
protected const SLICE = 1;
protected const IN_STR = 2;
protected const IN_ARR = 3;
protected const IN_OBJ = 4;
protected const IN_CMT = 5;
/** @var int the value returned will be an array instead of a stdclass */
public const GET_ARRAY = 16;
/** @var int supresses the errors and replaces by a null */
public const SUPPRESS_ERRORS = 32;
/** @var int supports to-json */
public const USE_TO_JSON = 64;
/** @var int If the fix value is broken, then it adds [] or {} automatically */
public const DECODE_FIX_ROOT = 128;
public const DECODE_NO_QUOTE = 256;
protected static int $use = 0;
// private - cache the mbstring lookup results..
protected static bool $_mb_strlen = false;
protected static bool $_mb_substr = false;
protected static bool $_mb_convert_encoding = false;
/** /**
* constructs a new JSON instance * constructs a new JSON instance. We force this library as static
* *
* @param int $use object behavior flags; combine with boolean-OR
*
* possible values:
* - SERVICES_JSON_LOOSE_TYPE: loose typing.
* "{...}" syntax creates associative arrays
* instead of objects in decode().
* - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
* Values which can't be encoded (e.g. resources)
* appear as NULL instead of throwing errors.
* By default, a deeply-nested resource will
* bubble up with an error, so all return values
* from encode() should be checked with isError()
* - SERVICES_JSON_USE_TO_JSON: call toJSON when serializing objects
* It serializes the return value from the toJSON call rather
* than the object it'self, toJSON can return associative arrays,
* strings or numbers, if you return an object, make sure it does
* not have a toJSON method, otherwise an error will occur.
*/ */
public function __construct($use = 0) protected function __construct()
{ {
$this->use = $use;
$this->_mb_strlen = function_exists('mb_strlen');
$this->_mb_convert_encoding = function_exists('mb_convert_encoding');
$this->_mb_substr = function_exists('mb_substr');
} }
// private - cache the mbstring lookup results.. protected static function init(?int $use = null): void
public $_mb_strlen = false; {
public $_mb_substr = false; if ($use !== null) {
public $_mb_convert_encoding = false; self::$use = $use;
}
self::$_mb_strlen = function_exists('mb_strlen');
self::$_mb_convert_encoding = function_exists('mb_convert_encoding');
self::$_mb_substr = function_exists('mb_substr');
}
/** /**
* convert a string from one UTF-16 char to one UTF-8 char * convert a string from one UTF-16 char to one UTF-8 char
@@ -166,28 +129,25 @@ class Services_JSON
* @return string UTF-8 character * @return string UTF-8 character
* @access private * @access private
*/ */
public function utf162utf8($utf16): string protected static function utf162utf8(string $utf16): string
{ {
if ($this->_mb_convert_encoding) { if (self::$_mb_convert_encoding) {
return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
} }
$bytes = (ord($utf16[0]) << 8) | ord($utf16[1]); $bytes = (ord($utf16[0]) << 8) | ord($utf16[1]);
switch (true) { switch (true) {
case ((0x7F & $bytes) == $bytes): case ((0x7F & $bytes) == $bytes):
// this case should never be reached, because we are in ASCII range // this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 // see: https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x7F & $bytes); return chr(0x7F & $bytes);
case (0x07FF & $bytes) == $bytes: case (0x07FF & $bytes) == $bytes:
// return a 2-byte UTF-8 character // return a 2-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 // see: https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xC0 | (($bytes >> 6) & 0x1F)) return chr(0xC0 | (($bytes >> 6) & 0x1F)) . chr(0x80 | ($bytes & 0x3F));
. chr(0x80 | ($bytes & 0x3F));
case (0xFFFF & $bytes) == $bytes: case (0xFFFF & $bytes) == $bytes:
// return a 3-byte UTF-8 character // return a 3-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 // see: https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xE0 | (($bytes >> 12) & 0x0F)) return chr(0xE0 | (($bytes >> 12) & 0x0F)) . chr(0x80 | (($bytes >> 6) & 0x3F)) . chr(0x80 | ($bytes & 0x3F));
. chr(0x80 | (($bytes >> 6) & 0x3F))
. chr(0x80 | ($bytes & 0x3F));
} }
// ignoring UTF-32 for now, sorry // ignoring UTF-32 for now, sorry
return ''; return '';
@@ -199,35 +159,28 @@ class Services_JSON
* Normally should be handled by mb_convert_encoding, but * Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations * provides a slower PHP-only method for installations
* that lack the multibye string extension. * that lack the multibye string extension.
*
* @param string $utf8 UTF-8 character * @param string $utf8 UTF-8 character
* @return string UTF-16 character * @return string UTF-16 character
* @access private * @access private
*/ */
public function utf82utf16($utf8): string public static function utf82utf16(string $utf8): string
{ {
// oh please oh please oh please oh please oh please if (self::$_mb_convert_encoding) {
if ($this->_mb_convert_encoding) {
return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
} }
switch ($this->strlen8($utf8)) { switch (self::strlen8($utf8)) {
case 1: case 1:
// this case should never be reached, because we are in ASCII range // this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 // see: https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return $utf8; return $utf8;
case 2: case 2:
// return a UTF-16 character from a 2-byte UTF-8 char // return a UTF-16 character from a 2-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 // see: https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x07 & (ord($utf8[0]) >> 2)) return chr(0x07 & (ord($utf8[0]) >> 2)) . chr((0xC0 & (ord($utf8[0]) << 6)) | (0x3F & ord($utf8[1])));
. chr((0xC0 & (ord($utf8[0]) << 6))
| (0x3F & ord($utf8[1])));
case 3: case 3:
// return a UTF-16 character from a 3-byte UTF-8 char // return a UTF-16 character from a 3-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 // see: https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr((0xF0 & (ord($utf8[0]) << 4)) return chr((0xF0 & (ord($utf8[0]) << 4)) | (0x0F & (ord($utf8[1]) >> 2))) . chr((0xC0 & (ord($utf8[1]) << 6)) | (0x7F & ord($utf8[2])));
| (0x0F & (ord($utf8[1]) >> 2)))
. chr((0xC0 & (ord($utf8[1]) << 6))
| (0x7F & ord($utf8[2])));
} }
// ignoring UTF-32 for now, sorry // ignoring UTF-32 for now, sorry
return ''; return '';
@@ -244,14 +197,15 @@ class Services_JSON
* @return mixed JSON string representation of input var or an error if a problem occurs * @return mixed JSON string representation of input var or an error if a problem occurs
* @access public * @access public
*/ */
public function encode($var) public static function encode($var, $use = 0)
{ {
//header('Content-type: application/json'); //header('Content-type: application/json');
return $this->encodeUnsafe($var); self::init($use);
return self::encodeUnsafe($var);
} }
/** /**
* encodes an arbitrary variable into JSON format without JSON Header - warning - may allow XSS!!!!) * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow XSS!!!!
* *
* @param mixed $var any number, boolean, string, array, or object to be encoded. * @param mixed $var any number, boolean, string, array, or object to be encoded.
* see argument 1 to Services_JSON() above for array-parsing behavior. * see argument 1 to Services_JSON() above for array-parsing behavior.
@@ -261,12 +215,12 @@ class Services_JSON
* @return mixed JSON string representation of input var or an error if a problem occurs * @return mixed JSON string representation of input var or an error if a problem occurs
* @access public * @access public
*/ */
public function encodeUnsafe($var) public static function encodeUnsafe($var)
{ {
// see bug #16908 - regarding numeric locale printing // see bug #16908 - regarding numeric locale printing
$lc = setlocale(LC_NUMERIC, 0); $lc = setlocale(LC_NUMERIC, 0);
setlocale(LC_NUMERIC, 'C'); setlocale(LC_NUMERIC, 'C');
$ret = $this->_encode($var); $ret = self::_encode($var);
setlocale(LC_NUMERIC, $lc); setlocale(LC_NUMERIC, $lc);
return $ret; return $ret;
} }
@@ -282,7 +236,7 @@ class Services_JSON
* @return mixed JSON string representation of input var or an error if a problem occurs * @return mixed JSON string representation of input var or an error if a problem occurs
* @access public * @access public
*/ */
public function _encode($var) public static function _encode($var)
{ {
switch (gettype($var)) { switch (gettype($var)) {
case 'boolean': case 'boolean':
@@ -297,7 +251,7 @@ class Services_JSON
case 'string': case 'string':
// STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
$ascii = ''; $ascii = '';
$strlen_var = $this->strlen8($var); $strlen_var = self::strlen8($var);
/* /*
* Iterate over every character in the string, * Iterate over every character in the string,
* escaping with a slash or encoding to UTF-8 where necessary * escaping with a slash or encoding to UTF-8 where necessary
@@ -332,15 +286,15 @@ class Services_JSON
break; break;
case (($ord_var_c & 0xE0) == 0xC0): case (($ord_var_c & 0xE0) == 0xC0):
// characters U-00000080 - U-000007FF, mask 110XXXXX // characters U-00000080 - U-000007FF, mask 110XXXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 // see https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
if ($c + 1 >= $strlen_var) { if ($c + 1 >= $strlen_var) {
$c += 1; ++$c;
$ascii .= '?'; $ascii .= '?';
break; break;
} }
$char = pack('C*', $ord_var_c, ord($var[$c + 1])); $char = pack('C*', $ord_var_c, ord($var[$c + 1]));
$c += 1; ++$c;
$utf16 = $this->utf82utf16($char); $utf16 = self::utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16)); $ascii .= sprintf('\u%04s', bin2hex($utf16));
break; break;
case (($ord_var_c & 0xF0) == 0xE0): case (($ord_var_c & 0xF0) == 0xE0):
@@ -350,12 +304,10 @@ class Services_JSON
break; break;
} }
// characters U-00000800 - U-0000FFFF, mask 1110XXXX // characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 // see https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c, $char = pack('C*', $ord_var_c, @ord($var[$c + 1]), @ord($var[$c + 2]));
@ord($var[$c + 1]),
@ord($var[$c + 2]));
$c += 2; $c += 2;
$utf16 = $this->utf82utf16($char); $utf16 = self::utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16)); $ascii .= sprintf('\u%04s', bin2hex($utf16));
break; break;
case (($ord_var_c & 0xF8) == 0xF0): case (($ord_var_c & 0xF8) == 0xF0):
@@ -365,30 +317,23 @@ class Services_JSON
break; break;
} }
// characters U-00010000 - U-001FFFFF, mask 11110XXX // characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 // see https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c, $char = pack('C*', $ord_var_c, ord($var[$c + 1]), ord($var[$c + 2]), ord($var[$c + 3]));
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]));
$c += 3; $c += 3;
$utf16 = $this->utf82utf16($char); $utf16 = self::utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16)); $ascii .= sprintf('\u%04s', bin2hex($utf16));
break; break;
case (($ord_var_c & 0xFC) == 0xF8): case (($ord_var_c & 0xFC) == 0xF8):
// characters U-00200000 - U-03FFFFFF, mask 111110XX // characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 // see https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
if ($c + 4 >= $strlen_var) { if ($c + 4 >= $strlen_var) {
$c += 4; $c += 4;
$ascii .= '?'; $ascii .= '?';
break; break;
} }
$char = pack('C*', $ord_var_c, $char = pack('C*', $ord_var_c, ord($var[$c + 1]), ord($var[$c + 2]), ord($var[$c + 3]), ord($var[$c + 4]));
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]),
ord($var[$c + 4]));
$c += 4; $c += 4;
$utf16 = $this->utf82utf16($char); $utf16 = self::utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16)); $ascii .= sprintf('\u%04s', bin2hex($utf16));
break; break;
case (($ord_var_c & 0xFE) == 0xFC): case (($ord_var_c & 0xFE) == 0xFC):
@@ -398,15 +343,10 @@ class Services_JSON
break; break;
} }
// characters U-04000000 - U-7FFFFFFF, mask 1111110X // characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 // see https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c, $char = pack('C*', $ord_var_c, ord($var[$c + 1]), ord($var[$c + 2]), ord($var[$c + 3]), ord($var[$c + 4]), ord($var[$c + 5]));
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]),
ord($var[$c + 4]),
ord($var[$c + 5]));
$c += 5; $c += 5;
$utf16 = $this->utf82utf16($char); $utf16 = self::utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16)); $ascii .= sprintf('\u%04s', bin2hex($utf16));
break; break;
} }
@@ -415,7 +355,7 @@ class Services_JSON
case 'array': case 'array':
/* /*
* As per JSON spec if any array key is not an integer * As per JSON spec if any array key is not an integer
* we must treat the the whole array as an object. We * we must treat then the whole array as an object. We
* also try to catch a sparsely populated associative * also try to catch a sparsely populated associative
* array with numeric keys here because some JS engines * array with numeric keys here because some JS engines
* will create an array with empty indexes up to * will create an array with empty indexes up to
@@ -429,57 +369,51 @@ class Services_JSON
* ECMA reserved word or starts with a digit the * ECMA reserved word or starts with a digit the
* parameter is only accessible using ECMAScript's * parameter is only accessible using ECMAScript's
* bracket notation. * bracket notation.
*/ */ // treat as a JSON object
// treat as a JSON object
if (is_array($var) && count($var) && (array_keys($var) !== range(0, count($var) - 1))) { if (is_array($var) && count($var) && (array_keys($var) !== range(0, count($var) - 1))) {
$properties = array_map(array($this, 'name_value'), $properties = array_map([self::class, 'name_value'], array_keys($var), array_values($var));
array_keys($var),
array_values($var));
foreach ($properties as $property) { foreach ($properties as $property) {
if ($this->isError($property)) { if (self::isError($property)) {
return $property; return $property;
} }
} }
return '{' . implode(',', $properties) . '}'; return '{' . implode(',', $properties) . '}';
} }
// treat it like a regular array // treat it like a regular array
$elements = array_map(array($this, '_encode'), $var); $elements = array_map([self::class, '_encode'], $var);
foreach ($elements as $element) { foreach ($elements as $element) {
if ($this->isError($element)) { if (self::isError($element)) {
return $element; return $element;
} }
} }
return '[' . implode(',', $elements) . ']'; return '[' . implode(',', $elements) . ']';
case 'object': case 'object':
// support toJSON methods. // support toJSON methods.
if (($this->use & SERVICES_JSON_USE_TO_JSON) && method_exists($var, 'toJSON')) { if ((self::$use & self::USE_TO_JSON) && method_exists($var, 'toJSON')) {
// this may end up allowing unlimited recursion // this may end up allowing unlimited recursion, so we check the return value to make sure it's
// so we check the return value to make sure it's not got the same method. // not got the same method.
$recode = $var->toJSON(); $recode = $var->toJSON();
if (method_exists($recode, 'toJSON')) { if (method_exists($recode, 'toJSON')) {
if (($this->use & SERVICES_JSON_SUPPRESS_ERRORS)) { if ((self::$use & self::SUPPRESS_ERRORS)) {
return 'null'; return 'null';
} }
$this->Services_JSON_Error(get_class($var) . self::Services_JSON_Error(get_class($var) . " toJSON returned an object with a toJSON method.");
" toJSON returned an object with a toJSON method.");
} }
return $this->_encode($recode); return self::_encode($recode);
} }
$vars = get_object_vars($var); $vars = get_object_vars($var);
$properties = array_map(array($this, 'name_value'), $properties = array_map([self::class, 'name_value'], array_keys($vars), array_values($vars));
array_keys($vars),
array_values($vars));
foreach ($properties as $property) { foreach ($properties as $property) {
if ($this->isError($property)) { if (self::isError($property)) {
return $property; return $property;
} }
} }
return '{' . implode(',', $properties) . '}'; return '{' . implode(',', $properties) . '}';
default: default:
if (($this->use & SERVICES_JSON_SUPPRESS_ERRORS)) { if ((self::$use & self::SUPPRESS_ERRORS)) {
return 'null'; return 'null';
} }
$this->Services_JSON_Error(gettype($var) . " can not be encoded as JSON string"); self::Services_JSON_Error(gettype($var) . " can not be encoded as JSON string");
} }
} }
@@ -492,13 +426,13 @@ class Services_JSON
* @return string JSON-formatted name-value pair, like '"name":value' * @return string JSON-formatted name-value pair, like '"name":value'
* @access private * @access private
*/ */
public function name_value($name, $value) protected static function name_value(string $name, $value)
{ {
$encoded_value = $this->_encode($value); $encoded_value = self::_encode($value);
if ($this->isError($encoded_value)) { if (self::isError($encoded_value)) {
return $encoded_value; return $encoded_value;
} }
return $this->_encode(strval($name)) . ':' . $encoded_value; return self::_encode($name) . ':' . $encoded_value;
} }
/** /**
@@ -509,16 +443,12 @@ class Services_JSON
* @return string string value stripped of comments and whitespace * @return string string value stripped of comments and whitespace
* @access private * @access private
*/ */
public function reduce_string($str): string protected static function reduce_string(string $str): string
{ {
$str = preg_replace(array( $str = preg_replace([// eliminate single line comments in '// ...' form
// eliminate single line comments in '// ...' form '#^\s*//(.+)$#m', // eliminate multi-line comments in '/* ... */' form, at start of string
'#^\s*//(.+)$#m', '#^\s*/\*(.+)\*/#Us', // eliminate multi-line comments in '/* ... */' form, at end of string
// eliminate multi-line comments in '/* ... */' form, at start of string '#/\*(.+)\*/\s*$#Us'], '', $str);
'#^\s*/\*(.+)\*/#Us',
// eliminate multi-line comments in '/* ... */' form, at end of string
'#/\*(.+)\*/\s*$#Us'
), '', $str);
// eliminate extraneous space // eliminate extraneous space
return trim($str); return trim($str);
} }
@@ -526,19 +456,67 @@ class Services_JSON
/** /**
* decodes a JSON string into appropriate variable * decodes a JSON string into appropriate variable
* *
* @param string $str JSON-formatted string * @param string $str JSON-formatted string
* @param bool $asAssocArray if true, then it returns as an associative array.<br> * @param int $use object behavior flags; combine with boolean-OR possible values:<br>
* if false (default), then it will return as an stdClass * - <b>self::GET_ARRAY</b>: syntax creates associative arrays<br>
* @return mixed number, boolean, string, array, or object * instead of objects in decode().<br>
* corresponding to given JSON input string. * - <b>self::SUPPRESS_ERRORS</b>: error suppression.<br>
* See argument 1 to Services_JSON() above for object-output behavior. * Values which can't be encoded (e.g. resources)<br>
* Note that decode() always returns strings * appear as NULL instead of throwing errors.<br>
* in ASCII or UTF-8 format! * By default, a deeply-nested resource will<br>
* @access public * bubble up with an error, so all return values<br>
* from encode() should be checked with isError()<br>
* - <b>self::USE_TO_JSON</b>: call toJSON when serializing objects<br>
* It serializes the return value from the toJSON call rather<br>
* than the object it'self, toJSON can return associative arrays,<br>
* strings or numbers, if you return an object, make sure it does<br>
* not have a toJSON method, otherwise an error will occur.<br>
* -<b>self::DECODE_FIX_ROOT</b>: Fix the code if the root parenthesis are
* missing<br> Example: "1,2,3" works as "[1,2,3]" and "a:1,b:2" works as "{a:1,b:2}"
*
* @return array|bool|float|int|stdClass|string|null number, boolean, string, array, or object<br>
* corresponding to given JSON input string.<br>
* See argument 1 to Services_JSON() above for object-output behavior.<br>
* Note that decode() always returns strings<br>
* in ASCII or UTF-8 format!<br>
* @access public
*/ */
public function decode($str, $asAssocArray = false) public static function decode(string $str, int $use = 0)
{ {
$str = $this->reduce_string($str); if ($use & self::DECODE_FIX_ROOT) {
$str = trim($str);
if($str==="") {
throw new RuntimeException("no value to decode");
}
$firstChar = $str[0];
if ($firstChar !== '{' && $firstChar !== '[') {
// fixing a malformed json-u, if the json-u doesn't start with { [ and ends with ] } then it wraps it
// note:: it will fail for a simple value su as "hello", so it will return ["hello"]
$p0 = strpos($str, ':'); // content:2,content2:3 => {content:2,content2:3}
$p0 = $p0 === false ? PHP_INT_MAX : $p0;
$p1 = strpos($str, ','); // 2,a:3 => [2,a:3]
$p1 = $p1 === false ? PHP_INT_MAX : $p1;
if ($p0 < $p1) {
$str = '{' . $str . '}';
} else {
$str = '[' . $str . ']';
}
}
}
self::init($use);
return self::decode2($str);
}
/**
* It is used internally.
* @param $str
* @return array|bool|float|int|stdClass|string|null
*
* @noinspection PhpUndefinedVariableInspection
*/
protected static function decode2($str)
{
$str = self::reduce_string($str);
switch (strtolower($str)) { switch (strtolower($str)) {
case 'true': case 'true':
return true; return true;
@@ -547,25 +525,23 @@ class Services_JSON
case 'null': case 'null':
return null; return null;
default: default:
$m = array(); $m = [];
if (is_numeric($str)) { if (is_numeric($str)) {
// Lookie-loo, it's a number // Lookie-loo, it's a number
// This would work on its own, but I'm trying to be // This would work on its own, but I'm trying to be
// good about returning integers where appropriate: // good about returning integers where appropriate:
// return (float)$str; // return (float)$str;
// Return float or int, as appropriate // Return float or int, as appropriate
return ((float)$str == (integer)$str) return ((float)$str == (integer)$str) ? (integer)$str : (float)$str;
? (integer)$str
: (float)$str;
} }
if (preg_match('/^(["\']).*(\1)$/s', $str, $m) && $m[1] == $m[2]) { if (preg_match('/^(["\']).*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
// STRINGS RETURNED IN UTF-8 FORMAT // STRINGS RETURNED IN UTF-8 FORMAT
$delim = $this->substr8($str, 0, 1); $delim = self::substr8($str, 0, 1);
$chrs = $this->substr8($str, 1, -1); $chrs = self::substr8($str, 1, -1);
$utf8 = ''; $utf8 = '';
$strlen_chrs = $this->strlen8($chrs); $strlen_chrs = self::strlen8($chrs);
for ($c = 0; $c < $strlen_chrs; ++$c) { for ($c = 0; $c < $strlen_chrs; ++$c) {
$substr_chrs_c_2 = $this->substr8($chrs, $c, 2); $substr_chrs_c_2 = self::substr8($chrs, $c, 2);
$ord_chrs_c = ord($chrs[$c]); $ord_chrs_c = ord($chrs[$c]);
switch (true) { switch (true) {
case $substr_chrs_c_2 === '\b': case $substr_chrs_c_2 === '\b':
@@ -592,16 +568,14 @@ class Services_JSON
case $substr_chrs_c_2 === '\\\'': case $substr_chrs_c_2 === '\\\'':
case $substr_chrs_c_2 === '\\\\': case $substr_chrs_c_2 === '\\\\':
case $substr_chrs_c_2 === '\\/': case $substr_chrs_c_2 === '\\/':
if (($delim === '"' && $substr_chrs_c_2 !== '\\\'') || if (($delim === '"' && $substr_chrs_c_2 !== '\\\'') || ($delim === "'" && $substr_chrs_c_2 !== '\\"')) {
($delim === "'" && $substr_chrs_c_2 !== '\\"')) {
$utf8 .= $chrs[++$c]; $utf8 .= $chrs[++$c];
} }
break; break;
case preg_match('/\\\u[0-9A-F]{4}/i', $this->substr8($chrs, $c, 6)): case preg_match('/\\\u[0-9A-F]{4}/i', self::substr8($chrs, $c, 6)):
// single, escaped unicode character // single, escaped unicode character
$utf16 = chr(hexdec($this->substr8($chrs, ($c + 2), 2))) $utf16 = chr(hexdec(self::substr8($chrs, ($c + 2), 2))) . chr(hexdec(self::substr8($chrs, ($c + 4), 2)));
. chr(hexdec($this->substr8($chrs, ($c + 4), 2))); $utf8 .= self::utf162utf8($utf16);
$utf8 .= $this->utf162utf8($utf16);
$c += 5; $c += 5;
break; break;
case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
@@ -609,32 +583,32 @@ class Services_JSON
break; break;
case ($ord_chrs_c & 0xE0) == 0xC0: case ($ord_chrs_c & 0xE0) == 0xC0:
// characters U-00000080 - U-000007FF, mask 110XXXXX // characters U-00000080 - U-000007FF, mask 110XXXXX
//see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 //see https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= $this->substr8($chrs, $c, 2); $utf8 .= self::substr8($chrs, $c, 2);
++$c; ++$c;
break; break;
case ($ord_chrs_c & 0xF0) == 0xE0: case ($ord_chrs_c & 0xF0) == 0xE0:
// characters U-00000800 - U-0000FFFF, mask 1110XXXX // characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 // see https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= $this->substr8($chrs, $c, 3); $utf8 .= self::substr8($chrs, $c, 3);
$c += 2; $c += 2;
break; break;
case ($ord_chrs_c & 0xF8) == 0xF0: case ($ord_chrs_c & 0xF8) == 0xF0:
// characters U-00010000 - U-001FFFFF, mask 11110XXX // characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 // see https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= $this->substr8($chrs, $c, 4); $utf8 .= self::substr8($chrs, $c, 4);
$c += 3; $c += 3;
break; break;
case ($ord_chrs_c & 0xFC) == 0xF8: case ($ord_chrs_c & 0xFC) == 0xF8:
// characters U-00200000 - U-03FFFFFF, mask 111110XX // characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 // see https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= $this->substr8($chrs, $c, 5); $utf8 .= self::substr8($chrs, $c, 5);
$c += 4; $c += 4;
break; break;
case ($ord_chrs_c & 0xFE) == 0xFC: case ($ord_chrs_c & 0xFE) == 0xFC:
// characters U-04000000 - U-7FFFFFFF, mask 1111110X // characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 // see https://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= $this->substr8($chrs, $c, 6); $utf8 .= self::substr8($chrs, $c, 6);
$c += 5; $c += 5;
break; break;
} }
@@ -644,156 +618,116 @@ class Services_JSON
if (preg_match('/^\[.*]$/s', $str) || preg_match('/^\{.*}$/s', $str)) { if (preg_match('/^\[.*]$/s', $str) || preg_match('/^\{.*}$/s', $str)) {
// array, or object notation // array, or object notation
if ($str[0] === '[') { if ($str[0] === '[') {
$stk = array(SERVICES_JSON_IN_ARR); $stk = [self::IN_ARR];
$arr = array(); $arr = [];
} else { } else {
if ($this->use & SERVICES_JSON_LOOSE_TYPE) { $stk = [self::IN_OBJ];
$stk = array(SERVICES_JSON_IN_OBJ); if (self::$use & self::GET_ARRAY) {
$obj = array(); $obj = [];
} else { } else {
$stk = array(SERVICES_JSON_IN_OBJ);
$obj = new stdClass(); $obj = new stdClass();
} }
} }
$stk[] = array('what' => SERVICES_JSON_SLICE, $stk[] = ['what' => self::SLICE, 'where' => 0, 'delim' => false];
'where' => 0, $chrs = self::substr8($str, 1, -1);
'delim' => false); $chrs = self::reduce_string($chrs);
$chrs = $this->substr8($str, 1, -1);
$chrs = $this->reduce_string($chrs);
if ($chrs == '') { if ($chrs == '') {
if (reset($stk) == SERVICES_JSON_IN_ARR) { if (reset($stk) == self::IN_ARR) {
return $arr; return $arr;
} }
return $obj; return $obj;
} }
//print("\nparsing {$chrs}\n"); $strlen_chrs = self::strlen8($chrs);
$strlen_chrs = $this->strlen8($chrs);
for ($c = 0; $c <= $strlen_chrs; ++$c) { for ($c = 0; $c <= $strlen_chrs; ++$c) {
$top = end($stk); $top = end($stk);
$substr_chrs_c_2 = $this->substr8($chrs, $c, 2); $substr_chrs_c_2 = self::substr8($chrs, $c, 2);
if (($c == $strlen_chrs) || (($chrs[$c] === ',') && ($top['what'] == SERVICES_JSON_SLICE))) { if (($c == $strlen_chrs) || (($chrs[$c] === ',') && ($top['what'] == self::SLICE))) {
// found a comma that is not inside a string, array, etc., // found a comma that is not inside a string, array, etc.,
// OR we've reached the end of the character list // OR we've reached the end of the character list
$slice = $this->substr8($chrs, $top['where'], ($c - $top['where'])); $slice = self::substr8($chrs, $top['where'], ($c - $top['where']));
$stk[] = array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false); $stk[] = ['what' => self::SLICE, 'where' => ($c + 1), 'delim' => false];
//print("Found split at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); if (reset($stk) == self::IN_ARR) {
if (reset($stk) == SERVICES_JSON_IN_ARR) {
// we are in an array, so just push an element onto the stack // we are in an array, so just push an element onto the stack
$arr[] = $this->decode($slice); $arr[] = self::decode2($slice);
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { } elseif (reset($stk) == self::IN_OBJ) {
// we are in an object, so figure // we are in an object, so figure out the property name and set an
// out the property name and set an // element in an associative array,for now
// element in an associative array, $parts = [];
// for now /** @noinspection NotOptimalRegularExpressionsInspection */
$parts = array();
if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:/Uis', $slice, $parts)) { if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:/Uis', $slice, $parts)) {
// "name":value pair // "name":value pair
$key = $this->decode($parts[1]); $key = self::decode2($parts[1]);
$val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B")); $val = self::decode2(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B"));
if ($this->use & SERVICES_JSON_LOOSE_TYPE) { if (self::$use & self::GET_ARRAY) {
$obj[$key] = $val; $obj[$key] = $val;
} else { } else {
$obj->$key = $val; $obj->$key = $val;
} }
} elseif (preg_match('/^\s*(\w+)\s*:/Uis', $slice, $parts)) { } /** @noinspection NotOptimalRegularExpressionsInspection */ elseif (preg_match('/^\s*([a-z.@A-Z0-9_]+)\s*:/Ui', $slice, $parts)) {
// name:value pair, where name is unquoted // name:value pair, where name is unquoted
$key = $parts[1]; $key = $parts[1];
$val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B")); $val = self::decode2(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B"));
if ($this->use & SERVICES_JSON_LOOSE_TYPE) { if (self::$use & self::GET_ARRAY) {
$obj[$key] = $val; $obj[$key] = $val;
} else { } else {
$obj->$key = $val; $obj->$key = $val;
} }
} }
} }
} elseif ((($chrs[$c] === '"') || ($chrs[$c] === "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { } elseif ((($chrs[$c] === '"') || ($chrs[$c] === "'")) && ($top['what'] != self::IN_STR)) {
// found a quote, and we are not inside a string // found a quote, and we are not inside a string
$stk[] = array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs[$c]); $stk[] = ['what' => self::IN_STR, 'where' => $c, 'delim' => $chrs[$c]];
//print("Found start of string at {$c}\n"); } elseif (($chrs[$c] == $top['delim']) && ($top['what'] == self::IN_STR) && ((self::strlen8(self::substr8($chrs, 0, $c)) - self::strlen8(rtrim(self::substr8($chrs, 0, $c), '\\'))) % 2 != 1)) {
} elseif (($chrs[$c] == $top['delim']) &&
($top['what'] == SERVICES_JSON_IN_STR) &&
(($this->strlen8($this->substr8($chrs, 0, $c)) - $this->strlen8(rtrim($this->substr8($chrs, 0, $c), '\\'))) % 2 != 1)) {
// found a quote, we're in a string, and it's not escaped // found a quote, we're in a string, and it's not escaped
// we know that it's not escaped becase there is _not_ an // we know that it's not escaped becase there is _not_ an
// odd number of backslashes at the end of the string so far // odd number of backslashes at the end of the string so far
array_pop($stk); array_pop($stk);
//print("Found end of string at {$c}: ".$this->substr8($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); } elseif (($chrs[$c] === '[') && in_array($top['what'], [self::SLICE, self::IN_ARR, self::IN_OBJ])) {
} elseif (($chrs[$c] === '[') &&
in_array($top['what'], [SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ])) {
// found a left-bracket, and we are in an array, object, or slice // found a left-bracket, and we are in an array, object, or slice
$stk[] = array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false); $stk[] = ['what' => self::IN_ARR, 'where' => $c, 'delim' => false];
//print("Found start of array at {$c}\n"); } elseif (($chrs[$c] === ']') && ($top['what'] == self::IN_ARR)) {
} elseif (($chrs[$c] === ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
// found a right-bracket, and we're in an array // found a right-bracket, and we're in an array
array_pop($stk); array_pop($stk);
//print("Found end of array at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); } elseif (($chrs[$c] === '{') && in_array($top['what'], [self::SLICE, self::IN_ARR, self::IN_OBJ])) {
} elseif (($chrs[$c] === '{') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a left-brace, and we are in an array, object, or slice // found a left-brace, and we are in an array, object, or slice
$stk[] = array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false); $stk[] = ['what' => self::IN_OBJ, 'where' => $c, 'delim' => false];
//print("Found start of object at {$c}\n"); } elseif (($chrs[$c] === '}') && ($top['what'] == self::IN_OBJ)) {
} elseif (($chrs[$c] === '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
// found a right-brace, and we're in an object // found a right-brace, and we're in an object
array_pop($stk); array_pop($stk);
//print("Found end of object at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); } elseif (($substr_chrs_c_2 === '/*') && in_array($top['what'], [self::SLICE, self::IN_ARR, self::IN_OBJ])) {
} elseif (($substr_chrs_c_2 === '/*') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a comment start, and we are in an array, object, or slice // found a comment start, and we are in an array, object, or slice
$stk[] = array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false); $stk[] = ['what' => self::IN_CMT, 'where' => $c, 'delim' => false];
$c++; $c++;
//print("Found start of comment at {$c}\n"); } elseif (($substr_chrs_c_2 === '*/') && ($top['what'] == self::IN_CMT)) {
} elseif (($substr_chrs_c_2 === '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
// found a comment end, and we're in one now // found a comment end, and we're in one now
array_pop($stk); array_pop($stk);
$c++; $c++;
for ($i = $top['where']; $i <= $c; ++$i) for ($i = $top['where']; $i <= $c; ++$i) {
$chrs = substr_replace($chrs, ' ', $i, 1); $chrs = substr_replace($chrs, ' ', $i, 1);
//print("Found end of comment at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); }
} }
} }
if (reset($stk) == SERVICES_JSON_IN_ARR) { if (reset($stk) == self::IN_ARR) {
return $arr; return $arr;
} }
if (reset($stk) == SERVICES_JSON_IN_OBJ) { if (reset($stk) == self::IN_OBJ) {
if ($asAssocArray) {
return $this->arrayCastRecursive($obj);
}
return $obj; return $obj;
} }
} }
if(self::$use & self::DECODE_NO_QUOTE) {
return $str;
}
} }
return null; return null;
} }
public function arrayCastRecursive($array) protected static function isError($data, $code = null): bool
{
if (is_array($array)) {
foreach ($array as $key => $value) {
if (is_array($value)) {
$array[$key] = $this->arrayCastRecursive($value);
}
if ($value instanceof stdClass) {
$array[$key] = $this->arrayCastRecursive((array)$value);
}
}
}
if ($array instanceof stdClass) {
return $this->arrayCastRecursive((array)$array);
}
return $array;
}
/**
* @todo Ultimately, this should just call PEAR::isError()
*/
public function isError($data, $code = null): bool
{ {
if (class_exists('pear')) { if (class_exists('pear')) {
/** @noinspection PhpUndefinedClassInspection */ /** @noinspection PhpUndefinedClassInspection */
return PEAR::isError($data, $code); return PEAR::isError($data, $code);
} }
if (is_object($data) && (get_class($data) === 'services_json_error' || if (is_object($data) && (get_class($data) === 'services_json_error' || is_subclass_of($data, 'services_json_error'))) {
is_subclass_of($data, 'services_json_error'))) {
return true; return true;
} }
return false; return false;
@@ -804,9 +738,9 @@ class Services_JSON
* @param string $str * @param string $str
* @return integer length * @return integer length
*/ */
public function strlen8($str): int protected static function strlen8(string $str): int
{ {
if ($this->_mb_strlen) { if (self::$_mb_strlen) {
return mb_strlen($str, "8bit"); return mb_strlen($str, "8bit");
} }
return strlen($str); return strlen($str);
@@ -816,21 +750,21 @@ class Services_JSON
* Returns part of a string, interpreting $start and $length as number of bytes. * Returns part of a string, interpreting $start and $length as number of bytes.
* @param string $string * @param string $string
* @param integer $start start * @param integer $start start
* @param integer $length length * @param integer|bool $length length
* @return string length * @return string length
*/ */
public function substr8($string, $start, $length = false): string protected static function substr8(string $string, int $start, $length = false): string
{ {
if ($length === false) { if ($length === false) {
$length = $this->strlen8($string) - $start; $length = self::strlen8($string) - $start;
} }
if ($this->_mb_substr) { if (self::$_mb_substr) {
return mb_substr($string, $start, $length, "8bit"); return mb_substr($string, $start, $length, "8bit");
} }
return substr($string, $start, $length); return substr($string, $start, $length);
} }
public function Services_JSON_Error($msg): void protected static function Services_JSON_Error($msg): void
{ {
throw new RuntimeException($msg); throw new RuntimeException($msg);
} }
+90
View File
@@ -0,0 +1,90 @@
<?php declare(strict_types=1);
use eftec\ServicesJson\Services_JSON;
use PHPUnit\Framework\TestCase;
final class FirstTest extends TestCase
{
public function testDecode(): void
{
$this->assertEquals(
(object)[ 'k1' => "v1",'k2'=>'v2']
,Services_JSON::decode('{"k1":"v1","k2":"v2"}')
);
$this->assertEquals(
(object)[ 'k1' => "v1\n\t\f\r",'k2'=>'v2']
,Services_JSON::decode('{"k1":"v1\n\t\f\r","k2":"v2"}')
);
$this->assertEquals(
[ 'k1' => 'v1','k2'=>2]
,Services_JSON::decode('{"k1":"v1","k2":2}',Services_JSON::GET_ARRAY)
);
$this->assertEquals(
[ 'k1' => 'v1','k2'=>2]
,Services_JSON::decode('{"k1":"v1","k2":2 /* comment */}',Services_JSON::GET_ARRAY)
);
$this->assertEquals(
[ 'k1' => 'v1','k2'=>2]
,Services_JSON::decode('{k1:"v1",k2:2}',Services_JSON::GET_ARRAY)
);
$this->assertEquals(
[ 'k1' => 'v1','k2'=>2]
,Services_JSON::decode('"k1":"v1", k2:2',
Services_JSON::GET_ARRAY | Services_JSON::DECODE_FIX_ROOT)
);
//
$this->assertEquals(
[ 'button1' =>["aaaa","bbb","cccc"],
'button2' =>["aaaa","bbb","cccc"],
'button3' =>["aaaa","bbb","cccc"],]
,Services_JSON::decode('{"button1":["aaaa","bbb","cccc"],"button2":["aaaa","bbb","cccc"],"button3":["aaaa","bbb","cccc"]}',
Services_JSON::GET_ARRAY | Services_JSON::DECODE_FIX_ROOT)
);
$this->assertEquals(
[ 1,2,3]
,Services_JSON::decode('1,2,3',
Services_JSON::GET_ARRAY | Services_JSON::DECODE_FIX_ROOT)
);
$this->assertEquals(
[1]
,Services_JSON::decode('1',
Services_JSON::GET_ARRAY | Services_JSON::DECODE_FIX_ROOT)
);
$this->assertEquals(
['a'=>1]
,Services_JSON::decode('a:1',
Services_JSON::GET_ARRAY | Services_JSON::DECODE_FIX_ROOT)
);
$this->assertEquals(
['a.b@c'=>1]
,Services_JSON::decode('a.b@c:1',
Services_JSON::GET_ARRAY | Services_JSON::DECODE_FIX_ROOT)
);
}
public function testsimple(): void
{
$this->assertEquals(
['a'=>'aaa','b'=>'bbbb','c' => 'aaaaa']
,Services_JSON::decode('a:aaa,b:bbbb,c: aaaaa',
Services_JSON::GET_ARRAY | Services_JSON::DECODE_FIX_ROOT | Services_JSON::DECODE_NO_QUOTE));
$this->assertEquals(
['a'=>'aaa','b'=>'bbbb','c' => 'aaaaa']
,Services_JSON::decode('{"a":aaa,"b":bbbb,"c": aaaaa}', Services_JSON::GET_ARRAY | Services_JSON::DECODE_NO_QUOTE));
}
public function testEncode(): void
{
$this->assertEquals(
'{"k1":"v1","k2":"v2"}'
,Services_JSON::encode([ 'k1' => 'v1','k2'=>'v2'])
);
$this->assertEquals(
'{"k1":"v1","k2":[1,2,3]}'
,Services_JSON::encode((object)[ 'k1' => 'v1','k2'=>[1,2,3]])
);
$this->assertEquals(
'{"k\"1":"v1","k2":[1,2,3],"k3":{"k4":{"0":1,"1":2,"k5":[1,2]}}}'
,Services_JSON::encode((object)[ 'k"1' => 'v1','k2'=>[1,2,3],'k3'=>['k4'=>[1,2,'k5'=>[1,2]]]])
);
}
}