123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534 |
- <?php
- declare(strict_types=1);
- namespace JsonMachineTest;
- use JsonMachine\Exception\JsonMachineException;
- use JsonMachine\Exception\PathNotFoundException;
- use JsonMachine\Exception\SyntaxErrorException;
- use JsonMachine\Exception\UnexpectedEndSyntaxErrorException;
- use JsonMachine\JsonDecoder\ExtJsonDecoder;
- use JsonMachine\Parser;
- use JsonMachine\StringChunks;
- use JsonMachine\Tokens;
- use JsonMachine\TokensWithDebugging;
- /**
- * @covers \JsonMachine\Parser
- */
- class ParserTest extends \PHPUnit_Framework_TestCase
- {
- /**
- * @dataProvider data_testSyntax
- *
- * @param string $jsonPointer
- * @param string $json
- * @param array $expectedResult
- */
- public function testSyntax($jsonPointer, $json, $expectedResult)
- {
- $result = [];
- foreach ($this->createParser($json, $jsonPointer) as $key => $value) {
- $result[] = [$key => $value];
- }
- $this->assertSame($expectedResult, $result);
- }
- public function data_testSyntax()
- {
- return [
- ['', '{}', []],
- ['', '{"a": "b"}', [['a' => 'b']]],
- ['', '{"a":{"b":{"c":1}}}', [['a' => ['b' => ['c' => 1]]]]],
- ['', '[]', []],
- ['', '[null,true,false,"a",0,1,42.5]', [[0 => null], [1 => true], [2 => false], [3 => 'a'], [4 => 0], [5 => 1], [6 => 42.5]]],
- ['', '[{"c":1}]', [[['c' => 1]]]],
- ['', '[{"c":1},"string",{"d":2},false]', [[0 => ['c' => 1]], [1 => 'string'], [2 => ['d' => 2]], [3 => false]]],
- ['', '[false,{"c":1},"string",{"d":2}]', [[0 => false], [1 => ['c' => 1]], [2 => 'string'], [3 => ['d' => 2]]]],
- ['', '[{"c":1,"d":2}]', [[['c' => 1, 'd' => 2]]]],
- 'ISSUE-108' => [
- '',
- '["https://click.justwatch.com/a?cx=eyJzY2hlbWEiOiJpZ2x1OmNvbS5zbm93cGxvd2FuYWx5dGljcy5zbm93cGxvdy9jb250ZXh0cy9qc29uc2NoZW1hLzEtMC0wIiwiZGF0YSI6W3sic2NoZW1hIjoiaWdsdTpjb20uanVzdHdhdGNoL2NsaWNrb3V0X2NvbnRleHQvanNvbnNjaGVtYS8xLTItMCIsImRhdGEiOnsicHJvdmlkZXIiOiJBcHBsZSBUViIsIm1vbmV0aXphdGlvblR5cGUiOiJidXkiLCJwcmVzZW50YXRpb25UeXBlIjoiaGQiLCJjdXJyZW5jeSI6IlVTRCIsInByaWNlIjo1MTkuNzQsIm9yaWdpbmFsUHJpY2UiOjAsImF1ZGlvTGFuZ3VhZ2UiOiIiLCJzdWJ0aXRsZUxhbmd1YWdlIjoiIiwiY2luZW1hSWQiOjAsInNob3d0aW1lIjoiIiwiaXNGYXZvcml0ZUNpbmVtYSI6ZmFsc2UsInBhcnRuZXJJZCI6MTI3MCwicHJvdmlkZXJJZCI6MiwiY2xpY2tvdXRUeXBlIjoianctY29udGVudC1wYXJ0bmVyLWFwaSJ9fSx7InNjaGVtYSI6ImlnbHU6Y29tLmp1c3R3YXRjaC90aXRsZV9jb250ZXh0L2pzb25zY2hlbWEvMS0wLTAiLCJkYXRhIjp7InRpdGxlSWQiOjIwOTgxLCJvYmplY3RUeXBlIjoic2hvdyIsImp3RW50aXR5SWQiOiJ0czIwOTgxIn19XX0\u0026r=https%3A%2F%2Ftv.apple.com%2Fus%2Fshow%2Fsurvivor%2Fumc.cmc.6ozd0mt09a86bpa19l885jv4z\u0026uct_country=us"]',
- [['https://click.justwatch.com/a?cx=eyJzY2hlbWEiOiJpZ2x1OmNvbS5zbm93cGxvd2FuYWx5dGljcy5zbm93cGxvdy9jb250ZXh0cy9qc29uc2NoZW1hLzEtMC0wIiwiZGF0YSI6W3sic2NoZW1hIjoiaWdsdTpjb20uanVzdHdhdGNoL2NsaWNrb3V0X2NvbnRleHQvanNvbnNjaGVtYS8xLTItMCIsImRhdGEiOnsicHJvdmlkZXIiOiJBcHBsZSBUViIsIm1vbmV0aXphdGlvblR5cGUiOiJidXkiLCJwcmVzZW50YXRpb25UeXBlIjoiaGQiLCJjdXJyZW5jeSI6IlVTRCIsInByaWNlIjo1MTkuNzQsIm9yaWdpbmFsUHJpY2UiOjAsImF1ZGlvTGFuZ3VhZ2UiOiIiLCJzdWJ0aXRsZUxhbmd1YWdlIjoiIiwiY2luZW1hSWQiOjAsInNob3d0aW1lIjoiIiwiaXNGYXZvcml0ZUNpbmVtYSI6ZmFsc2UsInBhcnRuZXJJZCI6MTI3MCwicHJvdmlkZXJJZCI6MiwiY2xpY2tvdXRUeXBlIjoianctY29udGVudC1wYXJ0bmVyLWFwaSJ9fSx7InNjaGVtYSI6ImlnbHU6Y29tLmp1c3R3YXRjaC90aXRsZV9jb250ZXh0L2pzb25zY2hlbWEvMS0wLTAiLCJkYXRhIjp7InRpdGxlSWQiOjIwOTgxLCJvYmplY3RUeXBlIjoic2hvdyIsImp3RW50aXR5SWQiOiJ0czIwOTgxIn19XX0&r=https%3A%2F%2Ftv.apple.com%2Fus%2Fshow%2Fsurvivor%2Fumc.cmc.6ozd0mt09a86bpa19l885jv4z&uct_country=us']],
- ],
- ['/', '{"":{"c":1,"d":2}}', [['c' => 1], ['d' => 2]]],
- ['/~0', '{"~":{"c":1,"d":2}}', [['c' => 1], ['d' => 2]]],
- ['/~1', '{"/":{"c":1,"d":2}}', [['c' => 1], ['d' => 2]]],
- ['/~01', '{"~1":{"c":1,"d":2}}', [['c' => 1], ['d' => 2]]],
- ['/~00', '{"~0":{"c":1,"d":2}}', [['c' => 1], ['d' => 2]]],
- ['/path', '{"path":{"c":1,"d":2}}', [['c' => 1], ['d' => 2]]],
- ['/path', '{"no":[null], "path":{"c":1,"d":2}}', [['c' => 1], ['d' => 2]]],
- ['/0', '[{"c":1,"d":2}, [null]]', [['c' => 1], ['d' => 2]]],
- ['/0/path', '[{"path":{"c":1,"d":2}}]', [['c' => 1], ['d' => 2]]],
- ['/1/path', '[[null], {"path":{"c":1,"d":2}}]', [['c' => 1], ['d' => 2]]],
- ['/path/0', '{"path":[{"c":1,"d":2}, [null]]}', [['c' => 1], ['d' => 2]]],
- ['/path/1', '{"path":[null,{"c":1,"d":2}, [null]]}', [['c' => 1], ['d' => 2]]],
- ['/path/to', '{"path":{"to":{"c":1,"d":2}}}', [['c' => 1], ['d' => 2]]],
- ['/path/after-vector', '{"path":{"array":[],"after-vector":{"c":1,"d":2}}}', [['c' => 1], ['d' => 2]]],
- ['/path/after-vector', '{"path":{"array":["item"],"after-vector":{"c":1,"d":2}}}', [['c' => 1], ['d' => 2]]],
- ['/path/after-vector', '{"path":{"object":{"item":null},"after-vector":{"c":1,"d":2}}}', [['c' => 1], ['d' => 2]]],
- ['/path/after-vectors', '{"path":{"array":[],"object":{},"after-vectors":{"c":1,"d":2}}}', [['c' => 1], ['d' => 2]]],
- ['/0/0', '[{"0":{"c":1,"d":2}}]', [['c' => 1], ['d' => 2]]],
- ['/1/1', '[0,{"1":{"c":1,"d":2}}]', [['c' => 1], ['d' => 2]]],
- 'PR-19-FIX' => ['/datafeed/programs/1', file_get_contents(__DIR__.'/PR-19-FIX.json'), [['program_info' => ['id' => 'X1']]]],
- 'ISSUE-41-FIX' => ['/path', '{"path":[{"empty":{}},{"value":1}]}', [[['empty' => []]], [1 => ['value' => 1]]]],
- ['/-', '[{"one": 1,"two": 2},{"three": 3,"four": 4}]', [['one' => 1], ['two' => 2], ['three' => 3], ['four' => 4]]],
- ['/zero/-', '{"zero":[{"one": 1,"two": 2},{"three": 3,"four": 4}]}', [['one' => 1], ['two' => 2], ['three' => 3], ['four' => 4]]],
- ['/zero/-/three', '{"zero":[{"one": 1,"two": 2},{"three": 3,"four": 4}]}', [['three' => 3]]],
- 'ISSUE-62#1' => ['/-/id', '[ {"id":125}, {"id":785}, {"id":459}, {"id":853} ]', [['id' => 125], ['id' => 785], ['id' => 459], ['id' => 853]]],
- 'ISSUE-62#2' => ['/key/-/id', '{"key": [ {"id":125}, {"id":785}, {"id":459}, {"id":853} ]}', [['id' => 125], ['id' => 785], ['id' => 459], ['id' => 853]]],
- [
- ['/meta_data', '/data/companies'],
- '{"meta_data": {"total_rows": 2},"data": {"type": "companies","companies": [{"id": "1","company": "Company 1"},{"id": "2","company": "Company 2"}]}}',
- [
- ['total_rows' => 2],
- ['0' => ['id' => '1', 'company' => 'Company 1']],
- ['1' => ['id' => '2', 'company' => 'Company 2']],
- ],
- ],
- [
- ['/-/id', '/-/company'],
- '[{"id": "1","company": "Company 1"},{"id": "2","company": "Company 2"}]',
- [
- ['id' => '1'],
- ['company' => 'Company 1'],
- ['id' => '2'],
- ['company' => 'Company 2'],
- ],
- ],
- [
- ['/-/id', '/0/company'],
- '[{"id": "1","company": "Company 1"},{"id": "2","company": "Company 2"}]',
- [
- ['id' => '1'],
- ['company' => 'Company 1'],
- ['id' => '2'],
- ],
- ],
- ];
- }
- /**
- * @dataProvider data_testThrowsOnNotFoundJsonPointer
- *
- * @param string $json
- * @param string $jsonPointer
- */
- public function testThrowsOnNotFoundJsonPointer($json, $jsonPointer)
- {
- $parser = $this->createParser($json, $jsonPointer);
- $this->expectException(PathNotFoundException::class);
- $this->expectExceptionMessage("Paths '".implode(', ', (array) $jsonPointer)."' were not found in json stream.");
- iterator_to_array($parser);
- }
- public function data_testThrowsOnNotFoundJsonPointer()
- {
- return [
- 'non existing pointer' => ['{}', '/not/found'],
- "empty string should not match '0'" => ['{"0":[]}', '/'],
- 'empty string should not match 0 index' => ['[[]]', '/'],
- '0 should not match empty string' => ['{"":[]}', '/0'],
- ];
- }
- /**
- * @dataProvider data_testSyntaxError
- *
- * @param string $malformedJson
- */
- public function testSyntaxError($malformedJson)
- {
- $this->expectException(SyntaxErrorException::class);
- iterator_to_array($this->createParser($malformedJson));
- }
- public function data_testSyntaxError()
- {
- return [
- ['[}'],
- ['{]'],
- ['null'],
- ['true'],
- ['false'],
- ['0'],
- ['100'],
- ['"string"'],
- ['}'],
- [']'],
- [','],
- [':'],
- [''],
- ['[null null]'],
- ['["string" "string"]'],
- ['[,"string","string"]'],
- ['["string",,"string"]'],
- ['["string","string",]'],
- ['["string",1eeee1]'],
- ['{"key\u000Z": "non hex key"}'],
- ];
- }
- /**
- * @dataProvider data_testUnexpectedEndError
- *
- * @param string $malformedJson
- */
- public function testUnexpectedEndError($malformedJson)
- {
- $this->expectException(UnexpectedEndSyntaxErrorException::class);
- iterator_to_array($this->createParser($malformedJson));
- }
- public function data_testUnexpectedEndError()
- {
- return [
- ['['],
- ['{'],
- ['["string"'],
- ['["string",'],
- ['[{"string":"string"}'],
- ['[{"string":"string"},'],
- ['[{"string":"string"},{'],
- ['[{"string":"string"},{"str'],
- ['[{"string":"string"},{"string"'],
- ['{"string"'],
- ['{"string":'],
- ['{"string":"string"'],
- ['{"string":["string","string"]'],
- ['{"string":["string","string"'],
- ['{"string":["string","string",'],
- ['{"string":["string","string","str'],
- ];
- }
- public function testGeneratorQuitsAfterFirstFoundCollectionHasBeenFinished()
- {
- $json = '
- {
- "results": [1],
- "other": [2],
- "results": [3]
- }
- ';
- $parser = $this->createParser($json, '/results');
- $this->assertSame([1], iterator_to_array($parser));
- }
- public function testScalarResult()
- {
- $result = $this->createParser('{"result":{"items": [1,2,3],"count": 3}}', '/result/count');
- $this->assertSame([3], iterator_to_array($result));
- }
- public function testScalarResultInArray()
- {
- $result = $this->createParser('{"result":[1,2,3]}', '/result/0');
- $this->assertSame([1], iterator_to_array($result));
- }
- public function testGeneratorQuitsAfterFirstScalarHasBeenFound()
- {
- $json = '
- {
- "result": "one",
- "other": [2],
- "result": "three"
- }
- ';
- $parser = $this->createParser($json, '/result');
- $this->assertSame(['result' => 'one'], iterator_to_array($parser));
- }
- public function testGeneratorYieldsNestedValues()
- {
- $json = '
- {
- "zero": [
- {
- "one": "ignored",
- "two": [
- {
- "three": 1
- }
- ],
- "four": [
- {
- "five": "ignored"
- }
- ]
- },
- {
- "one": 1,
- "two": [
- {
- "three": 2
- },
- {
- "three": 3
- }
- ],
- "four": [
- {
- "five": "ignored"
- }
- ]
- }
- ]
- }
- ';
- $parser = $this->createParser($json, '/zero/-/two/-/three');
- $actual = [];
- $expected = ['three' => [1, 2, 3]];
- foreach ($parser as $key => $value) {
- $actual[$key][] = $value;
- }
- $this->assertSame($expected, $actual);
- }
- public function testGeneratorYieldsNestedValuesOfMultiplePaths()
- {
- $json = '
- {
- "zero": [
- {
- "one": "hello",
- "two": [
- {
- "three": 1
- }
- ],
- "four": [
- {
- "five": "ignored"
- }
- ]
- },
- {
- "one": "bye",
- "two": [
- {
- "three": 2
- },
- {
- "three": 3
- }
- ],
- "four": [
- {
- "five": "ignored"
- }
- ]
- }
- ]
- }
- ';
- $parser = $this->createParser($json, ['/zero/-/one', '/zero/-/two/-/three']);
- $actual = [];
- $expected = ['one' => ['hello', 'bye'], 'three' => [1, 2, 3]];
- foreach ($parser as $key => $value) {
- $actual[$key][] = $value;
- }
- $this->assertSame($expected, $actual);
- }
- private function createParser($json, $jsonPointer = '')
- {
- return new Parser(new Tokens(new \ArrayIterator([$json])), $jsonPointer, new ExtJsonDecoder(true));
- }
- public function testDefaultDecodingStructureIsObject()
- {
- $items = new Parser(new Tokens(new StringChunks('[{"key": "value"}]')));
- foreach ($items as $item) {
- $this->assertEquals((object) ['key' => 'value'], $item);
- }
- }
- /**
- * @dataProvider data_testGetCurrentJsonPointer
- */
- public function testGetCurrentJsonPointer($jsonPointer, string $json, array $currentJsonPointers)
- {
- $parser = $this->createParser($json, $jsonPointer);
- $i = 0;
- foreach ($parser as $value) {
- $this->assertEquals($currentJsonPointers[$i++], $parser->getCurrentJsonPointer());
- }
- }
- public function data_testGetCurrentJsonPointer()
- {
- return [
- ['', '{"c":1,"d":2}', ['', '']],
- ['/', '{"":{"c":1,"d":2}}', ['/', '/']],
- ['/~0', '{"~":{"c":1,"d":2}}', ['/~0', '/~0']],
- ['/~1', '{"/":{"c":1,"d":2}}', ['/~1', '/~1']],
- ['/~01', '{"~1":{"c":1,"d":2}}', ['/~01', '/~01']],
- ['/~00', '{"~0":{"c":1,"d":2}}', ['/~00', '/~00']],
- ['/~1/c', '{"/":{"c":[1,2],"d":2}}', ['/~1/c', '/~1/c']],
- ['/0', '[{"c":1,"d":2}, [null]]', ['/0', '/0']],
- ['/-', '[{"one": 1,"two": 2},{"three": 3,"four": 4}]', ['/0', '/0', '/1', '/1']],
- [
- ['/two', '/four'],
- '{"one": [1,11], "two": [2,22], "three": [3,33], "four": [4,44]}',
- ['/two', '/two', '/four', '/four'],
- ],
- [
- ['/-/two', '/-/one'],
- '[{"one": 1, "two": 2}, {"one": 1, "two": 2}]',
- ['/0/one', '/0/two', '/1/one', '/1/two'],
- ],
- ];
- }
- /**
- * @dataProvider data_testGetMatchedJsonPointer
- */
- public function testGetMatchedJsonPointer($jsonPointer, string $json, array $matchedJsonPointers)
- {
- $parser = $this->createParser($json, $jsonPointer);
- $i = 0;
- foreach ($parser as $value) {
- $this->assertEquals($matchedJsonPointers[$i++], $parser->getMatchedJsonPointer());
- }
- }
- public function data_testGetMatchedJsonPointer()
- {
- return [
- ['', '{"c":1,"d":2}', ['', '']],
- ['/', '{"":{"c":1,"d":2}}', ['/', '/']],
- ['/~0', '{"~":{"c":1,"d":2}}', ['/~0', '/~0']],
- ['/~1', '{"/":{"c":1,"d":2}}', ['/~1', '/~1']],
- ['/~01', '{"~1":{"c":1,"d":2}}', ['/~01', '/~01']],
- ['/~00', '{"~0":{"c":1,"d":2}}', ['/~00', '/~00']],
- ['/~1/c', '{"/":{"c":[1,2],"d":2}}', ['/~1/c', '/~1/c']],
- ['/0', '[{"c":1,"d":2}, [null]]', ['/0', '/0']],
- ['/-', '[{"one": 1,"two": 2},{"three": 3,"four": 4}]', ['/-', '/-', '/-', '/-']],
- [
- ['/two', '/four'],
- '{"one": [1,11], "two": [2,22], "three": [3,33], "four": [4,44]}',
- ['/two', '/two', '/four', '/four'],
- ],
- [
- ['/-/two', '/-/one'],
- '[{"one": 1, "two": 2}, {"one": 1, "two": 2}]',
- ['/-/one', '/-/two', '/-/one', '/-/two'],
- ],
- ];
- }
- public function testGetCurrentJsonPointerThrowsWhenCalledOutsideOfALoop()
- {
- $this->expectException(JsonMachineException::class);
- $this->expectExceptionMessage('must be called inside a loop');
- $parser = $this->createParser('[]');
- $parser->getCurrentJsonPointer();
- }
- public function testGetCurrentJsonPointerReturnsLiteralJsonPointer()
- {
- $parser = $this->createParser('{"\"key\\\\":"value"}', ['/\"key\\\\']);
- foreach ($parser as $key => $item) {
- $this->assertSame('/\"key\\\\', $parser->getCurrentJsonPointer());
- }
- }
- public function testGetMatchedJsonPointerThrowsWhenCalledOutsideOfALoop()
- {
- $this->expectException(JsonMachineException::class);
- $this->expectExceptionMessage('must be called inside a loop');
- $parser = $this->createParser('[]');
- $parser->getMatchedJsonPointer();
- }
- public function testGetMatchedJsonPointerReturnsLiteralMatch()
- {
- $parser = $this->createParser('{"\"key\\\\":"value"}', ['/\"key\\\\']);
- foreach ($parser as $key => $item) {
- $this->assertSame('/\"key\\\\', $parser->getMatchedJsonPointer());
- }
- }
- public function testGetJsonPointers()
- {
- $parser = $this->createParser('{}', ['/one', '/two']);
- $this->assertSame(['/one', '/two'], $parser->getJsonPointers());
- $parser = $this->createParser('{}');
- $this->assertSame([''], $parser->getJsonPointers());
- }
- public function testJsonPointerReferenceTokenMatchesJsonMemberNameLiterally()
- {
- $parser = $this->createParser('{"\\"key":"value"}', ['/\\"key']);
- foreach ($parser as $key => $item) {
- $this->assertSame('"key', $key);
- $this->assertSame('value', $item);
- }
- }
- public function testGetPositionReturnsCorrectPositionWithDebugEnabled()
- {
- $parser = new Parser(new TokensWithDebugging(['[ 1, "two", false ]']));
- $expectedPosition = [5, 12, 19];
- $this->assertSame(0, $parser->getPosition());
- foreach ($parser as $index => $item) {
- $this->assertSame($expectedPosition[$index], $parser->getPosition(), "index:$index, item:$item");
- }
- $this->assertSame(21, $parser->getPosition());
- }
- public function testGetPositionReturns0WithDebugDisabled()
- {
- $parser = new Parser(new Tokens(['[ 1, "two", false ]']));
- $this->assertSame(0, $parser->getPosition());
- foreach ($parser as $index => $item) {
- $this->assertSame(0, $parser->getPosition());
- }
- $this->assertSame(0, $parser->getPosition());
- }
- public function testGetPositionThrowsIfTokensDoNotSupportGetPosition()
- {
- $parser = new Parser(new \ArrayObject());
- $this->expectException(JsonMachineException::class);
- $parser->getPosition();
- }
- public function testThrowsMeaningfulErrorOnIncorrectTokens()
- {
- $parser = new Parser(new Tokens(['[$P]']));
- $this->expectException(SyntaxErrorException::class);
- foreach ($parser as $index => $item) {
- }
- }
- }
|