JsonParserTest.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. <?php
  2. /*
  3. * This file is part of the JSON Lint package.
  4. *
  5. * (c) Jordi Boggiano <j.boggiano@seld.be>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. use PHPUnit\Framework\TestCase;
  11. use Seld\JsonLint\JsonParser;
  12. use Seld\JsonLint\ParsingException;
  13. use Seld\JsonLint\DuplicateKeyException;
  14. class JsonParserTest extends TestCase
  15. {
  16. protected $json = array(
  17. '42', '42.3', '0.3', '-42', '-42.3', '-0.3',
  18. '2e1', '2E1', '-2e1', '-2E1', '2E+2', '2E-2', '-2E+2', '-2E-2',
  19. 'true', 'false', 'null', '""', '[]', '{}', '"string"',
  20. '["a", "sdfsd"]',
  21. '{"foo":"bar", "bar":"baz", "":"buz"}',
  22. '{"":"foo", "_empty_":"bar"}',
  23. '"\u00c9v\u00e9nement"',
  24. '"http:\/\/foo.com"',
  25. '"zo\\\\mg"',
  26. '{"test":"\u00c9v\u00e9nement"}',
  27. '["\u00c9v\u00e9nement"]',
  28. '"foo/bar"',
  29. '{"test":"http:\/\/foo\\\\zomg"}',
  30. '["http:\/\/foo\\\\zomg"]',
  31. '{"":"foo"}',
  32. '{"a":"b", "b":"c"}',
  33. '0',
  34. '""',
  35. '"\u0022"',
  36. '"Argument \u0022input\u0022 has an invalid value: ..."',
  37. '"👻"',
  38. '"\u1f47d"',
  39. );
  40. /**
  41. * @dataProvider provideValidStrings
  42. */
  43. public function testParsesValidStrings($input)
  44. {
  45. $parser = new JsonParser();
  46. $this->assertEquals(json_decode($input), $parser->parse($input));
  47. }
  48. public function provideValidStrings()
  49. {
  50. $strings = array();
  51. foreach ($this->json as $input) {
  52. $strings[] = array($input);
  53. }
  54. return $strings;
  55. }
  56. public function testErrorOnTrailingComma()
  57. {
  58. $parser = new JsonParser();
  59. try {
  60. $parser->parse('{
  61. "foo":"bar",
  62. }');
  63. $this->fail('Invalid trailing comma should be detected');
  64. } catch (ParsingException $e) {
  65. $this->assertContains('It appears you have an extra trailing comma', $e->getMessage());
  66. }
  67. }
  68. public function testErrorOnInvalidQuotes()
  69. {
  70. $parser = new JsonParser();
  71. try {
  72. $parser->parse('{
  73. "foo": \'bar\',
  74. }');
  75. $this->fail('Invalid quotes for string should be detected');
  76. } catch (ParsingException $e) {
  77. $this->assertContains('Invalid string, it appears you used single quotes instead of double quotes', $e->getMessage());
  78. }
  79. }
  80. public function testErrorOnUnescapedBackslash()
  81. {
  82. $parser = new JsonParser();
  83. try {
  84. $parser->parse('{
  85. "foo": "bar\z",
  86. }');
  87. $this->fail('Invalid unescaped string should be detected');
  88. } catch (ParsingException $e) {
  89. $this->assertContains('Invalid string, it appears you have an unescaped backslash at: \z', $e->getMessage());
  90. }
  91. }
  92. public function testErrorOnLongUnescapedBackslash()
  93. {
  94. $parser = new JsonParser();
  95. try {
  96. $parser->parse('{
  97. "#3026386-26 - Drush fatal error after upgrading to 8.6.6, 8.5.9, or 7.62: PHP Fatal error: Uncaught TYPO3\PharStreamWrapper\Exception": "https://www.drupal.org/files/issues/2019-01-16/d8-3026386-26.patch"
  98. }');
  99. $this->fail('Invalid unescaped string should be detected');
  100. } catch (ParsingException $e) {
  101. $expected = <<<'EXPECTED'
  102. Parse error on line 1:
  103. { "#3026386-26 - Drush
  104. ----^
  105. Invalid string, it appears you have an unescaped backslash at: \Phar
  106. EXPECTED;
  107. $this->assertEquals($expected, $e->getMessage());
  108. }
  109. }
  110. public function testErrorOnLongUnescapedBackslash2()
  111. {
  112. $parser = new JsonParser();
  113. try {
  114. $parser->parse('{
  115. "#3026386-26 - Drush fatal error after upgrading to 8.6.6, 8.5.9, or 7.62: PHP Fatal error": "https://www.drupal.org/files/issues/201\9-01-16/d8-3026386-26.patch
  116. }');
  117. $this->fail('Invalid unescaped string should be detected');
  118. } catch (ParsingException $e) {
  119. $expected = <<<'EXPECTED'
  120. Parse error on line 2:
  121. ...: PHP Fatal error": "https://www.drupal.
  122. ----------------------^
  123. Invalid string, it appears you have an unescaped backslash at: \9-01
  124. EXPECTED;
  125. $this->assertEquals($expected, $e->getMessage());
  126. }
  127. }
  128. public function testErrorOnUnterminatedString()
  129. {
  130. $parser = new JsonParser();
  131. try {
  132. $parser->parse('{"bar": "foo}');
  133. $this->fail('Invalid unterminated string should be detected');
  134. } catch (ParsingException $e) {
  135. $this->assertContains('Invalid string, it appears you forgot to terminate a string, or attempted to write a multiline string which is invalid', $e->getMessage());
  136. }
  137. }
  138. public function testErrorOnMultilineString()
  139. {
  140. $parser = new JsonParser();
  141. try {
  142. $parser->parse('{"bar": "foo
  143. bar"}');
  144. $this->fail('Invalid multi-line string should be detected');
  145. } catch (ParsingException $e) {
  146. $this->assertContains('Invalid string, it appears you forgot to terminate a string, or attempted to write a multiline string which is invalid', $e->getMessage());
  147. }
  148. }
  149. public function testErrorAtBeginning()
  150. {
  151. $parser = new JsonParser();
  152. try {
  153. $parser->parse('
  154. ');
  155. $this->fail('Empty string should be invalid');
  156. } catch (ParsingException $e) {
  157. $this->assertContains("Parse error on line 1:\n\n^", $e->getMessage());
  158. }
  159. }
  160. public function testParsesMultiInARow()
  161. {
  162. $parser = new JsonParser();
  163. foreach ($this->json as $input) {
  164. $this->assertEquals(json_decode($input), $parser->parse($input));
  165. }
  166. }
  167. public function testDetectsKeyOverrides()
  168. {
  169. $parser = new JsonParser();
  170. try {
  171. $parser->parse('{"a":"b", "a":"c"}', JsonParser::DETECT_KEY_CONFLICTS);
  172. $this->fail('Duplicate keys should not be allowed');
  173. } catch (DuplicateKeyException $e) {
  174. $this->assertContains('Duplicate key: a', $e->getMessage());
  175. $this->assertSame('a', $e->getKey());
  176. $this->assertSame(array('line' => 1, 'key' => 'a'), $e->getDetails());
  177. }
  178. }
  179. public function testDetectsKeyOverridesWithEmpty()
  180. {
  181. $parser = new JsonParser();
  182. if (PHP_VERSION_ID >= 70100) {
  183. $this->markTestSkipped('Only for PHP < 7.1');
  184. }
  185. try {
  186. $parser->parse('{"":"b", "_empty_":"a"}', JsonParser::DETECT_KEY_CONFLICTS);
  187. $this->fail('Duplicate keys should not be allowed');
  188. } catch (DuplicateKeyException $e) {
  189. $this->assertContains('Duplicate key: _empty_', $e->getMessage());
  190. $this->assertSame('_empty_', $e->getKey());
  191. $this->assertSame(array('line' => 1, 'key' => '_empty_'), $e->getDetails());
  192. }
  193. }
  194. public function testDuplicateKeys()
  195. {
  196. $parser = new JsonParser();
  197. $result = $parser->parse('{"a":"b", "a":"c", "a":"d"}', JsonParser::ALLOW_DUPLICATE_KEYS);
  198. $this->assertThat($result,
  199. $this->logicalAnd(
  200. $this->objectHasAttribute('a'),
  201. $this->objectHasAttribute('a.1'),
  202. $this->objectHasAttribute('a.2')
  203. )
  204. );
  205. }
  206. public function testDuplicateKeysWithEmpty()
  207. {
  208. $parser = new JsonParser();
  209. if (PHP_VERSION_ID >= 70100) {
  210. $this->markTestSkipped('Only for PHP < 7.1');
  211. }
  212. $result = $parser->parse('{"":"a", "_empty_":"b"}', JsonParser::ALLOW_DUPLICATE_KEYS);
  213. $this->assertThat($result,
  214. $this->logicalAnd(
  215. $this->objectHasAttribute('_empty_'),
  216. $this->objectHasAttribute('_empty_.1')
  217. )
  218. );
  219. }
  220. public function testParseToArray()
  221. {
  222. $parser = new JsonParser();
  223. $json = '{"one":"a", "two":{"three": "four"}, "": "empty"}';
  224. $result = $parser->parse($json, JsonParser::PARSE_TO_ASSOC);
  225. $this->assertSame(json_decode($json, true), $result);
  226. }
  227. public function testFileWithBOM()
  228. {
  229. try {
  230. $parser = new JsonParser();
  231. $parser->parse(file_get_contents(dirname(__FILE__) .'/bom.json'));
  232. $this->fail('BOM should be detected');
  233. } catch (ParsingException $e) {
  234. $this->assertContains('BOM detected', $e->getMessage());
  235. }
  236. }
  237. public function testLongString()
  238. {
  239. $parser = new JsonParser();
  240. $json = '{"k":"' . str_repeat("a\\n",10000) . '"}';
  241. $this->assertEquals(json_decode($json), $parser->parse($json));
  242. }
  243. }