Added FormatJson::parse( $value, $options = 0 ) returning Status
authorYuri Astrakhan <yurik@wikimedia.org>
Mon, 15 Sep 2014 23:53:49 +0000 (19:53 -0400)
committerJforrester <jforrester@wikimedia.org>
Fri, 26 Sep 2014 18:55:09 +0000 (18:55 +0000)
* Returns Status object that will contain decoded value on success
* Adds i18n messages for all available PHP JSON errors

ATTN Translation team: please copy these messages:

gwtoolset-json-error-depth => json-error-depth
gwtoolset-json-error-state-mismatch => json-error-state-mismatch
gwtoolset-json-error-ctrl-char => json-error-ctrl-char
gwtoolset-json-error-syntax => json-error-syntax
gwtoolset-json-error-utf8 => json-error-utf8

Change-Id: I1c4f37aaabad369b75a1fbd223fad27ebcfe1c3c

includes/json/FormatJson.php
languages/i18n/en.json
languages/i18n/qqq.json
tests/phpunit/includes/json/FormatJsonTest.php

index e45dd3a..5565644 100644 (file)
@@ -54,6 +54,15 @@ class FormatJson {
         */
        const ALL_OK = 3;
 
+       /**
+        * If set, treat json objects '{...}' as associative arrays. Without this option,
+        * json objects will be converted to stdClass.
+        * The value is set to 1 to be backward compatible with 'true' that was used before.
+        *
+        * @since 1.24
+        */
+       const FORCE_ASSOC = 1;
+
        /**
         * Regex that matches whitespace inside empty arrays and objects.
         *
@@ -127,6 +136,52 @@ class FormatJson {
                return json_decode( $value, $assoc );
        }
 
+       /**
+        * Decodes a JSON string.
+        *
+        * @param string $value The JSON string being decoded
+        * @param int $options A bit field that allows FORCE_ASSOC, TRY_FIXING, WRAP_RESULT
+        * For backward compatibility, FORCE_ASSOC is set to 1 to match the legacy 'true'
+        * @return Status If good, the value is available in $result->getValue()
+        */
+       public static function parse( $value, $options = 0 ) {
+               $assoc = ( $options & self::FORCE_ASSOC ) !== 0;
+               $result = json_decode( $value, $assoc );
+               $code = json_last_error();
+
+               switch ( $code ) {
+                       case JSON_ERROR_NONE:
+                               return Status::newGood( $result );
+                       default:
+                               return Status::newFatal( wfMessage( 'json-error-unknown' )->numParams( $code ) );
+                       case JSON_ERROR_DEPTH:
+                               $msg = 'json-error-depth';
+                               break;
+                       case JSON_ERROR_STATE_MISMATCH:
+                               $msg = 'json-error-state-mismatch';
+                               break;
+                       case JSON_ERROR_CTRL_CHAR:
+                               $msg = 'json-error-ctrl-char';
+                               break;
+                       case JSON_ERROR_SYNTAX:
+                               $msg = 'json-error-syntax';
+                               break;
+                       case JSON_ERROR_UTF8:
+                               $msg = 'json-error-utf8';
+                               break;
+                       case JSON_ERROR_RECURSION:
+                               $msg = 'json-error-recursion';
+                               break;
+                       case JSON_ERROR_INF_OR_NAN:
+                               $msg = 'json-error-inf-or-nan';
+                               break;
+                       case JSON_ERROR_UNSUPPORTED_TYPE:
+                               $msg = 'json-error-unsupported-type';
+                               break;
+               }
+               return Status::newFatal( $msg );
+       }
+
        /**
         * JSON encoder wrapper for PHP >= 5.4, which supports useful encoding options.
         *
index 12d1429..e8a5c3c 100644 (file)
        "mediastatistics-header-office": "Office",
        "mediastatistics-header-text": "Textual",
        "mediastatistics-header-executable": "Executables",
-       "mediastatistics-header-archive": "Compressed formats"
+       "mediastatistics-header-archive": "Compressed formats",
+       "json-error-unknown": "There was a problem with the JSON. Error: $1",
+       "json-error-depth": "The maximum stack depth has been exceeded",
+       "json-error-state-mismatch": "Invalid or malformed JSON",
+       "json-error-ctrl-char": "Control character error, possibly incorrectly encoded",
+       "json-error-syntax": "Syntax error",
+       "json-error-utf8": "Malformed UTF-8 characters, possibly incorrectly encoded",
+       "json-error-recursion": "One or more recursive references in the value to be encoded",
+       "json-error-inf-or-nan": "One or more NAN or INF values in the value to be encoded",
+       "json-error-unsupported-type": "A value of a type that cannot be encoded was given"
 }
index d480e2d..2049ae5 100644 (file)
        "mediastatistics-header-office": "Header on [[Special:MediaStatistics]] for file types that are in the Office category. This includes PDFs, OpenDocument files, Microsoft Word files, etc.",
        "mediastatistics-header-text": "Header on [[Special:MediaStatistics]] for file types that are in the text category. This includes simple text formats, including plain text formats, json, csv, and xml. Source code of compiled programming languages may be included here in the future, but isn't currently.",
        "mediastatistics-header-executable": "Header on [[Special:MediaStatistics]] for file types that are in the executable category. This includes things like source files for interpreted programming language (Shell scripts, javascript, etc).",
-       "mediastatistics-header-archive": "Header on [[Special:MediaStatistics]] for file types that are in the archive category. Includes things like tar, zip, gzip etc."
+       "mediastatistics-header-archive": "Header on [[Special:MediaStatistics]] for file types that are in the archive category. Includes things like tar, zip, gzip etc.",
+       "json-error-unknown": "User error message when there’s an unknown error.\n{{Identical|Unknown error}}. This error is shown if we received an unexpected value from PHP. See http://php.net/manual/en/function.json-last-error.php\n\nParameters:\n* $1 - integer error code",
+       "json-error-depth": "User error message when the maximum stack depth is exceeded.\nSee http://php.net/manual/en/function.json-last-error.php",
+       "json-error-state-mismatch": "User error message when underflow or the modes mismatch.\n\n'''Underflow''': A data-processing error arising when the absolute value of a computed quantity is smaller than the limits of precision of the computing device, retaining at least one significant digit.\nSee http://php.net/manual/en/function.json-last-error.php",
+       "json-error-ctrl-char": "User error message when an unexpected control character has been found.\nSee http://php.net/manual/en/function.json-last-error.php",
+       "json-error-syntax": "User error message when there is a syntax error; a malformed JSON.\nSee http://php.net/manual/en/function.json-last-error.php",
+       "json-error-utf8": "User error message when there are malformed UTF-8 characters, possibly incorrectly encoded.\nSee http://php.net/manual/en/function.json-last-error.php",
+       "json-error-recursion": "PHP JSON encoding/decoding error. See http://php.net/manual/en/function.json-last-error.php",
+       "json-error-inf-or-nan": "PHP JSON encoding/decoding error. See http://php.net/manual/en/function.json-last-error.php",
+       "json-error-unsupported-type": "PHP JSON encoding/decoding error. See http://php.net/manual/en/function.json-last-error.php"
 }
index bf58ee3..0f1cdf7 100644 (file)
@@ -123,6 +123,69 @@ class FormatJsonTest extends MediaWikiTestCase {
                );
        }
 
+       public static function provideParse() {
+               return array(
+                       array( null ),
+                       array( true ),
+                       array( false ),
+                       array( 0 ),
+                       array( 1 ),
+                       array( 1.2 ),
+                       array( '' ),
+                       array( 'str' ),
+                       array( array( 0, 1, 2 ) ),
+                       array( array( 'a' => 'b' ) ),
+                       array( array( 'a' => 'b' ) ),
+                       array( array( 'a' => 'b', 'x' => array( 'c' => 'd' ) ) ),
+               );
+       }
+
+       /**
+        * Recursively convert arrays into stdClass
+        * @param array|string|bool|int|float|null $value
+        * @return stdClass|string|bool|int|float|null
+        */
+       public static function toObject( $value ) {
+               return !is_array( $value ) ? $value : (object) array_map( __METHOD__, $value );
+       }
+
+       /**
+        * @dataProvider provideParse
+        * @param mixed $value
+        */
+       public function testParse( $value ) {
+               $expected = self::toObject( $value );
+               $json = FormatJson::encode( $expected, false, FormatJson::ALL_OK );
+               $this->assertJson( $json );
+
+               $st = FormatJson::parse( $json );
+               $this->assertType( 'Status', $st );
+               $this->assertTrue( $st->isGood() );
+               $this->assertEquals( $expected, $st->getValue() );
+
+               $st = FormatJson::parse( $json, FormatJson::FORCE_ASSOC );
+               $this->assertType( 'Status', $st );
+               $this->assertTrue( $st->isGood() );
+               $this->assertEquals( $value, $st->getValue() );
+       }
+
+       public static function provideParseErrors() {
+               return array(
+                       array( 'aaa' ),
+                       array( '{"j": 1 ] }' ),
+               );
+       }
+
+       /**
+        * @dataProvider provideParseErrors
+        * @param mixed $value
+        */
+       public function testParseErrors( $value ) {
+               $st = FormatJson::parse( $value );
+               $this->assertType( 'Status', $st );
+               $this->assertFalse( $st->isOK() );
+       }
+
        /**
         * Generate a set of test cases for a particular combination of encoder options.
         *