6 class FormatJsonTest
extends MediaWikiTestCase
{
8 public static function provideEncoderPrettyPrinting() {
21 * @dataProvider provideEncoderPrettyPrinting
23 public function testEncoderPrettyPrinting( $pretty, $expectedIndent ) {
25 'emptyObject' => new stdClass
,
27 'string' => 'foobar\\',
33 // Nested json works without problems
34 '"7":["8",{"9":"10"}]',
35 // Whitespace clean up doesn't touch strings that look alike
36 "{\n\t\"emptyObject\": {\n\t},\n\t\"emptyArray\": [ ]\n}",
40 // No trailing whitespace, no trailing linefeed
44 "string": "foobar\\\\",
50 "\"7\":[\"8\",{\"9\":\"10\"}]",
51 "{\n\t\"emptyObject\": {\n\t},\n\t\"emptyArray\": [ ]\n}"
55 $json = str_replace( "\r", '', $json ); // Windows compat
56 $json = str_replace( "\t", $expectedIndent, $json );
57 $this->assertSame( $json, FormatJson
::encode( $obj, $pretty ) );
60 public static function provideEncodeDefault() {
61 return self
::getEncodeTestCases( [] );
65 * @dataProvider provideEncodeDefault
67 public function testEncodeDefault( $from, $to ) {
68 $this->assertSame( $to, FormatJson
::encode( $from ) );
71 public static function provideEncodeUtf8() {
72 return self
::getEncodeTestCases( [ 'unicode' ] );
76 * @dataProvider provideEncodeUtf8
78 public function testEncodeUtf8( $from, $to ) {
79 $this->assertSame( $to, FormatJson
::encode( $from, false, FormatJson
::UTF8_OK
) );
82 public static function provideEncodeXmlMeta() {
83 return self
::getEncodeTestCases( [ 'xmlmeta' ] );
87 * @dataProvider provideEncodeXmlMeta
89 public function testEncodeXmlMeta( $from, $to ) {
90 $this->assertSame( $to, FormatJson
::encode( $from, false, FormatJson
::XMLMETA_OK
) );
93 public static function provideEncodeAllOk() {
94 return self
::getEncodeTestCases( [ 'unicode', 'xmlmeta' ] );
98 * @dataProvider provideEncodeAllOk
100 public function testEncodeAllOk( $from, $to ) {
101 $this->assertSame( $to, FormatJson
::encode( $from, false, FormatJson
::ALL_OK
) );
104 public function testEncodePhpBug46944() {
105 $this->assertNotEquals(
107 strtolower( FormatJson
::encode( "\xf0\xa0\x80\x80" ) ),
108 'Test encoding an broken json_encode character (U+20000)'
112 public function testDecodeReturnType() {
113 $this->assertInternalType(
115 FormatJson
::decode( '{"Name": "Cheeso", "Rank": 7}' ),
119 $this->assertInternalType(
121 FormatJson
::decode( '{"Name": "Cheeso", "Rank": 7}', true ),
126 public static function provideParse() {
139 [ [ 'a' => 'b', 'x' => [ 'c' => 'd' ] ] ],
144 * Recursively convert arrays into stdClass
145 * @param array|string|bool|int|float|null $value
146 * @return stdClass|string|bool|int|float|null
148 public static function toObject( $value ) {
149 return !is_array( $value ) ?
$value : (object)array_map( __METHOD__
, $value );
153 * @dataProvider provideParse
154 * @param mixed $value
156 public function testParse( $value ) {
157 $expected = self
::toObject( $value );
158 $json = FormatJson
::encode( $expected, false, FormatJson
::ALL_OK
);
159 $this->assertJson( $json );
161 $st = FormatJson
::parse( $json );
162 $this->assertInstanceOf( 'Status', $st );
163 $this->assertTrue( $st->isGood() );
164 $this->assertEquals( $expected, $st->getValue() );
166 $st = FormatJson
::parse( $json, FormatJson
::FORCE_ASSOC
);
167 $this->assertInstanceOf( 'Status', $st );
168 $this->assertTrue( $st->isGood() );
169 $this->assertEquals( $value, $st->getValue() );
173 * Test data for testParseTryFixing.
175 * Some PHP interpreters use json-c rather than the JSON.org cannonical
176 * parser to avoid being encumbered by the "shall be used for Good, not
177 * Evil" clause of the JSON.org parser's license. By default, json-c
178 * parses in a non-strict mode which allows trailing commas for array and
179 * object delarations among other things, so our JSON_ERROR_SYNTAX rescue
180 * block is not always triggered. It however isn't lenient in exactly the
181 * same ways as our TRY_FIXING mode, so the assertions in this test are
182 * a bit more complicated than they ideally would be:
184 * Optional third argument: true if json-c parses the value without
185 * intervention, false otherwise. Defaults to true.
187 * Optional fourth argument: expected cannonical JSON serialization of
188 * json-c parsed result. Defaults to the second argument's value.
190 public static function provideParseTryFixing() {
192 [ "[,]", '[]', false ],
193 [ "[ , ]", '[]', false ],
195 [ '[1],', false, true, '[1]' ],
200 [ "[1\n,\n]\n", '[1]' ],
201 [ '["a,",]', '["a,"]' ],
202 [ "[[1,]\n,[2,\n],[3\n,]]", '[[1],[2],[3]]' ],
203 // I wish we could parse this, but would need quote parsing
204 [ '[[1,],[2,],[3,]]', false, true, '[[1],[2],[3]]' ],
205 [ '[1,,]', false, false, '[1]' ],
210 * @dataProvider provideParseTryFixing
211 * @param string $value
212 * @param string|bool $expected Expected result with strict parser
213 * @param bool $jsoncParses Will json-c parse this value without TRY_FIXING?
214 * @param string|bool $expectedJsonc Expected result with lenient parser
215 * if different from the strict expectation
217 public function testParseTryFixing(
219 $jsoncParses = true, $expectedJsonc = null
221 // PHP5 results are always expected to have isGood() === false
222 $expectedGoodStatus = false;
224 // Check to see if json parser allows trailing commas
225 if ( json_decode( '[1,]' ) !== null ) {
226 // Use json-c specific expected result if provided
227 $expected = ( $expectedJsonc === null ) ?
$expected : $expectedJsonc;
228 // If json-c parses the value natively, expect isGood() === true
229 $expectedGoodStatus = $jsoncParses;
232 $st = FormatJson
::parse( $value, FormatJson
::TRY_FIXING
);
233 $this->assertInstanceOf( 'Status', $st );
234 if ( $expected === false ) {
235 $this->assertFalse( $st->isOK(), 'Expected isOK() == false' );
237 $this->assertSame( $expectedGoodStatus, $st->isGood(),
238 'Expected isGood() == ' . ( $expectedGoodStatus ?
'true' : 'false' )
240 $this->assertTrue( $st->isOK(), 'Expected isOK == true' );
241 $val = FormatJson
::encode( $st->getValue(), false, FormatJson
::ALL_OK
);
242 $this->assertEquals( $expected, $val );
246 public static function provideParseErrors() {
254 * @dataProvider provideParseErrors
255 * @param mixed $value
257 public function testParseErrors( $value ) {
258 $st = FormatJson
::parse( $value );
259 $this->assertInstanceOf( 'Status', $st );
260 $this->assertFalse( $st->isOK() );
263 public function provideStripComments() {
265 [ '{"a":"b"}', '{"a":"b"}' ],
266 [ "{\"a\":\"b\"}\n", "{\"a\":\"b\"}\n" ],
267 [ '/*c*/{"c":"b"}', '{"c":"b"}' ],
268 [ '{"a":"c"}/*c*/', '{"a":"c"}' ],
269 [ '/*c//d*/{"c":"b"}', '{"c":"b"}' ],
270 [ '{/*c*/"c":"b"}', '{"c":"b"}' ],
271 [ "/*\nc\r\n*/{\"c\":\"b\"}", '{"c":"b"}' ],
272 [ "//c\n{\"c\":\"b\"}", '{"c":"b"}' ],
273 [ "//c\r\n{\"c\":\"b\"}", '{"c":"b"}' ],
274 [ '{"a":"c"}//c', '{"a":"c"}' ],
275 [ "{\"a-c\"://c\n\"b\"}", '{"a-c":"b"}' ],
276 [ '{"/*a":"b"}', '{"/*a":"b"}' ],
277 [ '{"a":"//b"}', '{"a":"//b"}' ],
278 [ '{"a":"b/*c*/"}', '{"a":"b/*c*/"}' ],
279 [ "{\"\\\"/*a\":\"b\"}", "{\"\\\"/*a\":\"b\"}" ],
283 [ '"http://example.com"', '"http://example.com"' ],
285 [ '"Blåbærsyltetøy"', '"Blåbærsyltetøy"' ],
290 * @covers FormatJson::stripComments
291 * @dataProvider provideStripComments
292 * @param string $json
293 * @param string $expect
295 public function testStripComments( $json, $expect ) {
296 $this->assertSame( $expect, FormatJson
::stripComments( $json ) );
299 public function provideParseStripComments() {
301 [ '/* blah */true', true ],
302 [ "// blah \ntrue", true ],
303 [ '[ "a" , /* blah */ "b" ]', [ 'a', 'b' ] ],
308 * @covers FormatJson::parse
309 * @covers FormatJson::stripComments
310 * @dataProvider provideParseStripComments
311 * @param string $json
312 * @param mixed $expect
314 public function testParseStripComments( $json, $expect ) {
315 $st = FormatJson
::parse( $json, FormatJson
::STRIP_COMMENTS
);
316 $this->assertInstanceOf( 'Status', $st );
317 $this->assertTrue( $st->isGood() );
318 $this->assertEquals( $expect, $st->getValue() );
322 * Generate a set of test cases for a particular combination of encoder options.
324 * @param array $unescapedGroups List of character groups to leave unescaped
325 * @return array Arrays of unencoded strings and corresponding encoded strings
327 private static function getEncodeTestCases( array $unescapedGroups ) {
330 // Forward slash (always unescaped)
333 // Control characters
340 "\x1f" => '\u001f', // representative example
347 '\\\\' => '\\\\\\\\',
348 '\\u00e9' => '\\\u00e9', // security check for Unicode unescaping
351 "\xe2\x80\xa8" => '\u2028',
352 "\xe2\x80\xa9" => '\u2029',
355 "\xc3\xa9" => '\u00e9',
356 "\xf0\x9d\x92\x9e" => '\ud835\udc9e', // U+1D49E, outside the BMP
359 '<' => '\u003C', // JSON_HEX_TAG uses uppercase hex digits
366 foreach ( $groups as $name => $rules ) {
367 $leaveUnescaped = in_array( $name, $unescapedGroups );
368 foreach ( $rules as $from => $to ) {
369 $cases[] = [ $from, '"' . ( $leaveUnescaped ?
$from : $to ) . '"' ];