JSONPathTest.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. <?php
  2. namespace Flow\JSONPath\Test;
  3. require_once __DIR__ . "/../vendor/autoload.php";
  4. use Flow\JSONPath\JSONPath;
  5. use Flow\JSONPath\JSONPathLexer;
  6. use \Peekmo\JsonPath\JsonPath as PeekmoJsonPath;
  7. class JSONPathTest extends \PHPUnit_Framework_TestCase
  8. {
  9. /**
  10. * $.store.books[0].title
  11. */
  12. public function testChildOperators()
  13. {
  14. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find('$.store.books[0].title');
  15. $this->assertEquals('Sayings of the Century', $result[0]);
  16. }
  17. /**
  18. * $['store']['books'][0]['title']
  19. */
  20. public function testChildOperatorsAlt()
  21. {
  22. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$['store']['books'][0]['title']");
  23. $this->assertEquals('Sayings of the Century', $result[0]);
  24. }
  25. /**
  26. * $.array[start:end:step]
  27. */
  28. public function testFilterSliceA()
  29. {
  30. // Copy all items... similar to a wildcard
  31. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$['store']['books'][:].title");
  32. $this->assertEquals(['Sayings of the Century', 'Sword of Honour', 'Moby Dick', 'The Lord of the Rings'], $result->data());
  33. }
  34. public function testFilterSliceB()
  35. {
  36. // Fetch every second item starting with the first index (odd items)
  37. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$['store']['books'][1::2].title");
  38. $this->assertEquals(['Sword of Honour', 'The Lord of the Rings'], $result->data());
  39. }
  40. public function testFilterSliceC()
  41. {
  42. // Fetch up to the second index
  43. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$['store']['books'][0:2:1].title");
  44. $this->assertEquals(['Sayings of the Century', 'Sword of Honour', 'Moby Dick'], $result->data());
  45. }
  46. public function testFilterSliceD()
  47. {
  48. // Fetch up to the second index
  49. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$['store']['books'][-1:].title");
  50. $this->assertEquals(['The Lord of the Rings'], $result->data());
  51. }
  52. /**
  53. * Everything except the last 2 items
  54. */
  55. public function testFilterSliceE()
  56. {
  57. // Fetch up to the second index
  58. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$['store']['books'][:-2].title");
  59. $this->assertEquals(['Sayings of the Century', 'Sword of Honour'], $result->data());
  60. }
  61. /**
  62. * The Last item
  63. */
  64. public function testFilterSliceF()
  65. {
  66. // Fetch up to the second index
  67. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$['store']['books'][-1].title");
  68. $this->assertEquals(['The Lord of the Rings'], $result->data());
  69. }
  70. /**
  71. * $.store.books[(@.length-1)].title
  72. *
  73. * This notation is only partially implemented eg. hacked in
  74. */
  75. public function testChildQuery()
  76. {
  77. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$.store.books[(@.length-1)].title");
  78. $this->assertEquals(['The Lord of the Rings'], $result->data());
  79. }
  80. /**
  81. * $.store.books[?(@.price < 10)].title
  82. * Filter books that have a price less than 10
  83. */
  84. public function testQueryMatchLessThan()
  85. {
  86. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$.store.books[?(@.price < 10)].title");
  87. $this->assertEquals(['Sayings of the Century', 'Moby Dick'], $result->data());
  88. }
  89. /**
  90. * $..books[?(@.author == "J. R. R. Tolkien")]
  91. * Filter books that have a title equal to "..."
  92. */
  93. public function testQueryMatchEquals()
  94. {
  95. $results = (new JSONPath($this->exampleData(rand(0, 1))))->find('$..books[?(@.author == "J. R. R. Tolkien")].title');
  96. $this->assertEquals($results[0], 'The Lord of the Rings');
  97. }
  98. /**
  99. * $..books[?(@.author = 1)]
  100. * Filter books that have a title equal to "..."
  101. */
  102. public function testQueryMatchEqualsWithUnquotedInteger()
  103. {
  104. $results = (new JSONPath($this->exampleDataWithSimpleIntegers(rand(0, 1))))->find('$..features[?(@.value = 1)]');
  105. $this->assertEquals($results[0]->name, "foo");
  106. $this->assertEquals($results[1]->name, "baz");
  107. }
  108. /**
  109. * $..books[?(@.author != "J. R. R. Tolkien")]
  110. * Filter books that have a title not equal to "..."
  111. */
  112. public function testQueryMatchNotEqualsTo()
  113. {
  114. $results = (new JSONPath($this->exampleData(rand(0, 1))))->find('$..books[?(@.author != "J. R. R. Tolkien")].title');
  115. $this->assertcount(3, $results);
  116. $this->assertEquals(['Sayings of the Century', 'Sword of Honour', 'Moby Dick'], [$results[0], $results[1], $results[2]]);
  117. $results = (new JSONPath($this->exampleData(rand(0, 1))))->find('$..books[?(@.author !== "J. R. R. Tolkien")].title');
  118. $this->assertcount(3, $results);
  119. $this->assertEquals(['Sayings of the Century', 'Sword of Honour', 'Moby Dick'], [$results[0], $results[1], $results[2]]);
  120. $results = (new JSONPath($this->exampleData(rand(0, 1))))->find('$..books[?(@.author <> "J. R. R. Tolkien")].title');
  121. $this->assertcount(3, $results);
  122. $this->assertEquals(['Sayings of the Century', 'Sword of Honour', 'Moby Dick'], [$results[0], $results[1], $results[2]]);
  123. }
  124. /**
  125. * $.store.books[*].author
  126. */
  127. public function testWildcardAltNotation()
  128. {
  129. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$.store.books[*].author");
  130. $this->assertEquals(['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien'], $result->data());
  131. }
  132. /**
  133. * $..author
  134. */
  135. public function testRecursiveChildSearch()
  136. {
  137. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$..author");
  138. $this->assertEquals(['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien'], $result->data());
  139. }
  140. /**
  141. * $.store.*
  142. * all things in store
  143. * the structure of the example data makes this test look weird
  144. */
  145. public function testWildCard()
  146. {
  147. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$.store.*");
  148. if (is_object($result[0][0])) {
  149. $this->assertEquals('Sayings of the Century', $result[0][0]->title);
  150. } else {
  151. $this->assertEquals('Sayings of the Century', $result[0][0]['title']);
  152. }
  153. if (is_object($result[1])) {
  154. $this->assertEquals('red', $result[1]->color);
  155. } else {
  156. $this->assertEquals('red', $result[1]['color']);
  157. }
  158. }
  159. /**
  160. * $.store..price
  161. * the price of everything in the store.
  162. */
  163. public function testRecursiveChildSearchAlt()
  164. {
  165. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$.store..price");
  166. $this->assertEquals([8.95, 12.99, 8.99, 22.99, 19.95], $result->data());
  167. }
  168. /**
  169. * $..books[2]
  170. * the third book
  171. */
  172. public function testRecursiveChildSearchWithChildIndex()
  173. {
  174. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$..books[2].title");
  175. $this->assertEquals(["Moby Dick"], $result->data());
  176. }
  177. /**
  178. * $..books[(@.length-1)]
  179. */
  180. public function testRecursiveChildSearchWithChildQuery()
  181. {
  182. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$..books[(@.length-1)].title");
  183. $this->assertEquals(["The Lord of the Rings"], $result->data());
  184. }
  185. /**
  186. * $..books[-1:]
  187. * Resturn the last results
  188. */
  189. public function testRecursiveChildSearchWithSliceFilter()
  190. {
  191. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$..books[-1:].title");
  192. $this->assertEquals(["The Lord of the Rings"], $result->data());
  193. }
  194. /**
  195. * $..books[?(@.isbn)]
  196. * filter all books with isbn number
  197. */
  198. public function testRecursiveWithQueryMatch()
  199. {
  200. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$..books[?(@.isbn)].isbn");
  201. $this->assertEquals(['0-553-21311-3', '0-395-19395-8'], $result->data());
  202. }
  203. /**
  204. * $..*
  205. * All members of JSON structure
  206. */
  207. public function testRecursiveWithWildcard()
  208. {
  209. $result = (new JSONPath($this->exampleData(rand(0, 1))))->find("$..*");
  210. $result = json_decode(json_encode($result), true);
  211. $this->assertEquals('Sayings of the Century', $result[0]['books'][0]['title']);
  212. $this->assertEquals(19.95, $result[26]);
  213. }
  214. /**
  215. * Tests direct key access.
  216. */
  217. public function testSimpleArrayAccess()
  218. {
  219. $result = (new JSONPath(array('title' => 'test title')))->find('title');
  220. $this->assertEquals(array('test title'), $result->data());
  221. }
  222. public function testFilteringOnNoneArrays()
  223. {
  224. $data = ['foo' => 'asdf'];
  225. $result = (new JSONPath($data))->find("$.foo.bar");
  226. $this->assertEquals([], $result->data());
  227. }
  228. public function testMagicMethods()
  229. {
  230. $fooClass = new JSONPathTestClass();
  231. $results = (new JSONPath($fooClass, JSONPath::ALLOW_MAGIC))->find('$.foo');
  232. $this->assertEquals(['bar'], $results->data());
  233. }
  234. public function testMatchWithComplexSquareBrackets()
  235. {
  236. $result = (new JSONPath($this->exampleDataExtra()))->find("$['http://www.w3.org/2000/01/rdf-schema#label'][?(@['@language']='en')]['@language']");
  237. $this->assertEquals(["en"], $result->data());
  238. }
  239. public function testQueryMatchWithRecursive()
  240. {
  241. $locations = $this->exampleDataLocations();
  242. $result = (new JSONPath($locations))->find("..[?(@.type == 'suburb')].name");
  243. $this->assertEquals(["Rosebank"], $result->data());
  244. }
  245. public function testFirst()
  246. {
  247. $result = (new JSONPath($this->exampleDataExtra()))->find("$['http://www.w3.org/2000/01/rdf-schema#label'].*");
  248. $this->assertEquals(["@language" => "en"], $result->first()->data());
  249. }
  250. public function testLast()
  251. {
  252. $result = (new JSONPath($this->exampleDataExtra()))->find("$['http://www.w3.org/2000/01/rdf-schema#label'].*");
  253. $this->assertEquals(["@language" => "de"], $result->last()->data());
  254. }
  255. public function testSlashesInIndex()
  256. {
  257. $result = (new JSONPath($this->exampleDataWithSlashes()))->find("$['mediatypes']['image/png']");
  258. $this->assertEquals(
  259. [
  260. "/core/img/filetypes/image.png",
  261. ],
  262. $result->data()
  263. );
  264. }
  265. public function testOffsetUnset()
  266. {
  267. $data = array(
  268. "route" => array(
  269. array("name" => "A", "type" => "type of A"),
  270. array("name" => "B", "type" => "type of B")
  271. )
  272. );
  273. $data = json_encode($data);
  274. $jsonIterator = new JSONPath(json_decode($data));
  275. /** @var JSONPath $route */
  276. $route = $jsonIterator->offsetGet('route');
  277. $route->offsetUnset(0);
  278. $first = $route->first();
  279. $this->assertEquals("B", $first['name']);
  280. }
  281. public function testFirstKey()
  282. {
  283. // Array test for array
  284. $jsonPath = new JSONPath(['a' => 'A', 'b', 'B']);
  285. $firstKey = $jsonPath->firstKey();
  286. $this->assertEquals('a', $firstKey);
  287. // Array test for object
  288. $jsonPath = new JSONPath((object) ['a' => 'A', 'b', 'B']);
  289. $firstKey = $jsonPath->firstKey();
  290. $this->assertEquals('a', $firstKey);
  291. }
  292. public function testLastKey()
  293. {
  294. // Array test for array
  295. $jsonPath = new JSONPath(['a' => 'A', 'b' => 'B', 'c' => 'C']);
  296. $lastKey = $jsonPath->lastKey();
  297. $this->assertEquals('c', $lastKey);
  298. // Array test for object
  299. $jsonPath = new JSONPath((object) ['a' => 'A', 'b' => 'B', 'c' => 'C']);
  300. $lastKey = $jsonPath->lastKey();
  301. $this->assertEquals('c', $lastKey);
  302. }
  303. /**
  304. * Test: ensure trailing comma is stripped during parsing
  305. */
  306. public function testTrailingComma()
  307. {
  308. $jsonPath = new JSONPath(json_decode('{"store":{"book":[{"category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95},{"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99},{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99},{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":22.99}],"bicycle":{"color":"red","price":19.95}},"expensive":10}'));
  309. $result = $jsonPath->find("$..book[0,1,2,]");
  310. $this->assertCount(3, $result);
  311. }
  312. /**
  313. * Test: ensure negative indexes return -n from last index
  314. */
  315. public function testNegativeIndex()
  316. {
  317. $jsonPath = new JSONPath(json_decode('{"store":{"book":[{"category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95},{"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99},{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99},{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":22.99}],"bicycle":{"color":"red","price":19.95}},"expensive":10}'));
  318. $result = $jsonPath->find("$..book[-2]");
  319. $this->assertEquals("Herman Melville", $result[0]['author']);
  320. }
  321. public function testQueryAccessWithNumericalIndexes()
  322. {
  323. $jsonPath = new JSONPath(json_decode('{
  324. "result": {
  325. "list": [
  326. {
  327. "time": 1477526400,
  328. "o": "11.51000"
  329. },
  330. {
  331. "time": 1477612800,
  332. "o": "11.49870"
  333. }
  334. ]
  335. }
  336. }'));
  337. $result = $jsonPath->find("$.result.list[?(@.o == \"11.51000\")]");
  338. $this->assertEquals("11.51000", $result[0]->o);
  339. $jsonPath = new JSONPath(json_decode('{
  340. "result": {
  341. "list": [
  342. [
  343. 1477526400,
  344. "11.51000"
  345. ],
  346. [
  347. 1477612800,
  348. "11.49870"
  349. ]
  350. ]
  351. }
  352. }'));
  353. $result = $jsonPath->find("$.result.list[?(@[1] == \"11.51000\")]");
  354. $this->assertEquals("11.51000", $result[0][1]);
  355. }
  356. public function exampleData($asArray = true)
  357. {
  358. $json = '
  359. {
  360. "store":{
  361. "books":[
  362. {
  363. "category":"reference",
  364. "author":"Nigel Rees",
  365. "title":"Sayings of the Century",
  366. "price":8.95
  367. },
  368. {
  369. "category":"fiction",
  370. "author":"Evelyn Waugh",
  371. "title":"Sword of Honour",
  372. "price":12.99
  373. },
  374. {
  375. "category":"fiction",
  376. "author":"Herman Melville",
  377. "title":"Moby Dick",
  378. "isbn":"0-553-21311-3",
  379. "price":8.99
  380. },
  381. {
  382. "category":"fiction",
  383. "author":"J. R. R. Tolkien",
  384. "title":"The Lord of the Rings",
  385. "isbn":"0-395-19395-8",
  386. "price":22.99
  387. }
  388. ],
  389. "bicycle":{
  390. "color":"red",
  391. "price":19.95
  392. }
  393. }
  394. }';
  395. return json_decode($json, $asArray);
  396. }
  397. public function exampleDataExtra($asArray = true)
  398. {
  399. $json = '
  400. {
  401. "http://www.w3.org/2000/01/rdf-schema#label":[
  402. {
  403. "@language":"en"
  404. },
  405. {
  406. "@language":"de"
  407. }
  408. ]
  409. }
  410. ';
  411. return json_decode($json, $asArray);
  412. }
  413. public function exampleDataLocations($asArray = true)
  414. {
  415. $json = '
  416. {
  417. "name": "Gauteng",
  418. "type": "province",
  419. "child": {
  420. "name": "Johannesburg",
  421. "type": "city",
  422. "child": {
  423. "name": "Rosebank",
  424. "type": "suburb"
  425. }
  426. }
  427. }
  428. ';
  429. return json_decode($json, $asArray);
  430. }
  431. public function exampleDataWithSlashes($asArray = true)
  432. {
  433. $json = '
  434. {
  435. "features": [],
  436. "mediatypes": {
  437. "image/png": "/core/img/filetypes/image.png",
  438. "image/jpeg": "/core/img/filetypes/image.png",
  439. "image/gif": "/core/img/filetypes/image.png",
  440. "application/postscript": "/core/img/filetypes/image-vector.png"
  441. }
  442. }
  443. ';
  444. return json_decode($json, $asArray);
  445. }
  446. public function exampleDataWithSimpleIntegers($asArray = true)
  447. {
  448. $json = '
  449. {
  450. "features": [{"name": "foo", "value": 1},{"name": "bar", "value": 2},{"name": "baz", "value": 1}]
  451. }
  452. ';
  453. return json_decode($json, $asArray);
  454. }
  455. }
  456. class JSONPathTestClass
  457. {
  458. protected $attributes = [
  459. 'foo' => 'bar'
  460. ];
  461. public function __get($key)
  462. {
  463. return isset($this->attributes[$key]) ? $this->attributes[$key] : null;
  464. }
  465. }