query.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. var assert = require('assert');
  2. var jp = require('../');
  3. var data = require('./data/store.json');
  4. suite('query', function() {
  5. test('first-level member', function() {
  6. var results = jp.nodes(data, '$.store');
  7. assert.deepEqual(results, [ { path: ['$', 'store'], value: data.store } ]);
  8. });
  9. test('authors of all books in the store', function() {
  10. var results = jp.nodes(data, '$.store.book[*].author');
  11. assert.deepEqual(results, [
  12. { path: ['$', 'store', 'book', 0, 'author'], value: 'Nigel Rees' },
  13. { path: ['$', 'store', 'book', 1, 'author'], value: 'Evelyn Waugh' },
  14. { path: ['$', 'store', 'book', 2, 'author'], value: 'Herman Melville' },
  15. { path: ['$', 'store', 'book', 3, 'author'], value: 'J. R. R. Tolkien' }
  16. ]);
  17. });
  18. test('all authors', function() {
  19. var results = jp.nodes(data, '$..author');
  20. assert.deepEqual(results, [
  21. { path: ['$', 'store', 'book', 0, 'author'], value: 'Nigel Rees' },
  22. { path: ['$', 'store', 'book', 1, 'author'], value: 'Evelyn Waugh' },
  23. { path: ['$', 'store', 'book', 2, 'author'], value: 'Herman Melville' },
  24. { path: ['$', 'store', 'book', 3, 'author'], value: 'J. R. R. Tolkien' }
  25. ]);
  26. });
  27. test('all authors via subscript descendant string literal', function() {
  28. var results = jp.nodes(data, "$..['author']");
  29. assert.deepEqual(results, [
  30. { path: ['$', 'store', 'book', 0, 'author'], value: 'Nigel Rees' },
  31. { path: ['$', 'store', 'book', 1, 'author'], value: 'Evelyn Waugh' },
  32. { path: ['$', 'store', 'book', 2, 'author'], value: 'Herman Melville' },
  33. { path: ['$', 'store', 'book', 3, 'author'], value: 'J. R. R. Tolkien' }
  34. ]);
  35. });
  36. test('all things in store', function() {
  37. var results = jp.nodes(data, '$.store.*');
  38. assert.deepEqual(results, [
  39. { path: ['$', 'store', 'book'], value: data.store.book },
  40. { path: ['$', 'store', 'bicycle'], value: data.store.bicycle }
  41. ]);
  42. });
  43. test('price of everything in the store', function() {
  44. var results = jp.nodes(data, '$.store..price');
  45. assert.deepEqual(results, [
  46. { path: ['$', 'store', 'book', 0, 'price'], value: 8.95 },
  47. { path: ['$', 'store', 'book', 1, 'price'], value: 12.99 },
  48. { path: ['$', 'store', 'book', 2, 'price'], value: 8.99 },
  49. { path: ['$', 'store', 'book', 3, 'price'], value: 22.99 },
  50. { path: ['$', 'store', 'bicycle', 'price'], value: 19.95 }
  51. ]);
  52. });
  53. test('last book in order via expression', function() {
  54. var results = jp.nodes(data, '$..book[(@.length-1)]');
  55. assert.deepEqual(results, [ { path: ['$', 'store', 'book', 3], value: data.store.book[3] }]);
  56. });
  57. test('first two books via union', function() {
  58. var results = jp.nodes(data, '$..book[0,1]');
  59. assert.deepEqual(results, [
  60. { path: ['$', 'store', 'book', 0], value: data.store.book[0] },
  61. { path: ['$', 'store', 'book', 1], value: data.store.book[1] }
  62. ]);
  63. });
  64. test('first two books via slice', function() {
  65. var results = jp.nodes(data, '$..book[0:2]');
  66. assert.deepEqual(results, [
  67. { path: ['$', 'store', 'book', 0], value: data.store.book[0] },
  68. { path: ['$', 'store', 'book', 1], value: data.store.book[1] }
  69. ]);
  70. });
  71. test('filter all books with isbn number', function() {
  72. var results = jp.nodes(data, '$..book[?(@.isbn)]');
  73. assert.deepEqual(results, [
  74. { path: ['$', 'store', 'book', 2], value: data.store.book[2] },
  75. { path: ['$', 'store', 'book', 3], value: data.store.book[3] }
  76. ]);
  77. });
  78. test('filter all books with a price less than 10', function() {
  79. var results = jp.nodes(data, '$..book[?(@.price<10)]');
  80. assert.deepEqual(results, [
  81. { path: ['$', 'store', 'book', 0], value: data.store.book[0] },
  82. { path: ['$', 'store', 'book', 2], value: data.store.book[2] }
  83. ]);
  84. });
  85. test('first ten of all elements', function() {
  86. var results = jp.nodes(data, '$..*', 10);
  87. assert.deepEqual(results, [
  88. { path: [ '$', 'store' ], value: data.store },
  89. { path: [ '$', 'store', 'book' ], value: data.store.book },
  90. { path: [ '$', 'store', 'bicycle' ], value: data.store.bicycle },
  91. { path: [ '$', 'store', 'book', 0 ], value: data.store.book[0] },
  92. { path: [ '$', 'store', 'book', 1 ], value: data.store.book[1] },
  93. { path: [ '$', 'store', 'book', 2 ], value: data.store.book[2] },
  94. { path: [ '$', 'store', 'book', 3 ], value: data.store.book[3] },
  95. { path: [ '$', 'store', 'book', 0, 'category' ], value: 'reference' },
  96. { path: [ '$', 'store', 'book', 0, 'author' ], value: 'Nigel Rees' },
  97. { path: [ '$', 'store', 'book', 0, 'title' ], value: 'Sayings of the Century' }
  98. ])
  99. });
  100. test('all elements', function() {
  101. var results = jp.nodes(data, '$..*');
  102. assert.deepEqual(results, [
  103. { path: [ '$', 'store' ], value: data.store },
  104. { path: [ '$', 'store', 'book' ], value: data.store.book },
  105. { path: [ '$', 'store', 'bicycle' ], value: data.store.bicycle },
  106. { path: [ '$', 'store', 'book', 0 ], value: data.store.book[0] },
  107. { path: [ '$', 'store', 'book', 1 ], value: data.store.book[1] },
  108. { path: [ '$', 'store', 'book', 2 ], value: data.store.book[2] },
  109. { path: [ '$', 'store', 'book', 3 ], value: data.store.book[3] },
  110. { path: [ '$', 'store', 'book', 0, 'category' ], value: 'reference' },
  111. { path: [ '$', 'store', 'book', 0, 'author' ], value: 'Nigel Rees' },
  112. { path: [ '$', 'store', 'book', 0, 'title' ], value: 'Sayings of the Century' },
  113. { path: [ '$', 'store', 'book', 0, 'price' ], value: 8.95 },
  114. { path: [ '$', 'store', 'book', 1, 'category' ], value: 'fiction' },
  115. { path: [ '$', 'store', 'book', 1, 'author' ], value: 'Evelyn Waugh' },
  116. { path: [ '$', 'store', 'book', 1, 'title' ], value: 'Sword of Honour' },
  117. { path: [ '$', 'store', 'book', 1, 'price' ], value: 12.99 },
  118. { path: [ '$', 'store', 'book', 2, 'category' ], value: 'fiction' },
  119. { path: [ '$', 'store', 'book', 2, 'author' ], value: 'Herman Melville' },
  120. { path: [ '$', 'store', 'book', 2, 'title' ], value: 'Moby Dick' },
  121. { path: [ '$', 'store', 'book', 2, 'isbn' ], value: '0-553-21311-3' },
  122. { path: [ '$', 'store', 'book', 2, 'price' ], value: 8.99 },
  123. { path: [ '$', 'store', 'book', 3, 'category' ], value: 'fiction' },
  124. { path: [ '$', 'store', 'book', 3, 'author' ], value: 'J. R. R. Tolkien' },
  125. { path: [ '$', 'store', 'book', 3, 'title' ], value: 'The Lord of the Rings' },
  126. { path: [ '$', 'store', 'book', 3, 'isbn' ], value: '0-395-19395-8' },
  127. { path: [ '$', 'store', 'book', 3, 'price' ], value: 22.99 },
  128. { path: [ '$', 'store', 'bicycle', 'color' ], value: 'red' },
  129. { path: [ '$', 'store', 'bicycle', 'price' ], value: 19.95 }
  130. ]);
  131. });
  132. test('all elements via subscript wildcard', function() {
  133. var results = jp.nodes(data, '$..*');
  134. assert.deepEqual(jp.nodes(data, '$..[*]'), jp.nodes(data, '$..*'));
  135. });
  136. test('object subscript wildcard', function() {
  137. var results = jp.query(data, '$.store[*]');
  138. assert.deepEqual(results, [ data.store.book, data.store.bicycle ]);
  139. });
  140. test('no match returns empty array', function() {
  141. var results = jp.nodes(data, '$..bookz');
  142. assert.deepEqual(results, []);
  143. });
  144. test('member numeric literal gets first element', function() {
  145. var results = jp.nodes(data, '$.store.book.0');
  146. assert.deepEqual(results, [ { path: [ '$', 'store', 'book', 0 ], value: data.store.book[0] } ]);
  147. });
  148. test('member numeric literal matches string-numeric key', function() {
  149. var data = { authors: { '1': 'Herman Melville', '2': 'J. R. R. Tolkien' } };
  150. var results = jp.nodes(data, '$.authors.1');
  151. assert.deepEqual(results, [ { path: [ '$', 'authors', 1 ], value: 'Herman Melville' } ]);
  152. });
  153. test('descendant numeric literal gets first element', function() {
  154. var results = jp.nodes(data, '$.store.book..0');
  155. assert.deepEqual(results, [ { path: [ '$', 'store', 'book', 0 ], value: data.store.book[0] } ]);
  156. });
  157. test('root element gets us original obj', function() {
  158. var results = jp.nodes(data, '$');
  159. assert.deepEqual(results, [ { path: ['$'], value: data } ]);
  160. });
  161. test('subscript double-quoted string', function() {
  162. var results = jp.nodes(data, '$["store"]');
  163. assert.deepEqual(results, [ { path: ['$', 'store'], value: data.store} ]);
  164. });
  165. test('subscript single-quoted string', function() {
  166. var results = jp.nodes(data, "$['store']");
  167. assert.deepEqual(results, [ { path: ['$', 'store'], value: data.store} ]);
  168. });
  169. test('leading member component', function() {
  170. var results = jp.nodes(data, "store");
  171. assert.deepEqual(results, [ { path: ['$', 'store'], value: data.store} ]);
  172. });
  173. test('union of three array slices', function() {
  174. var results = jp.query(data, "$.store.book[0:1,1:2,2:3]");
  175. assert.deepEqual(results, data.store.book.slice(0,3));
  176. });
  177. test('slice with step > 1', function() {
  178. var results = jp.query(data, "$.store.book[0:4:2]");
  179. assert.deepEqual(results, [ data.store.book[0], data.store.book[2]]);
  180. });
  181. test('union of subscript string literal keys', function() {
  182. var results = jp.nodes(data, "$.store['book','bicycle']");
  183. assert.deepEqual(results, [
  184. { path: ['$', 'store', 'book'], value: data.store.book },
  185. { path: ['$', 'store', 'bicycle'], value: data.store.bicycle },
  186. ]);
  187. });
  188. test('union of subscript string literal three keys', function() {
  189. var results = jp.nodes(data, "$.store.book[0]['title','author','price']");
  190. assert.deepEqual(results, [
  191. { path: ['$', 'store', 'book', 0, 'title'], value: data.store.book[0].title },
  192. { path: ['$', 'store', 'book', 0, 'author'], value: data.store.book[0].author },
  193. { path: ['$', 'store', 'book', 0, 'price'], value: data.store.book[0].price }
  194. ]);
  195. });
  196. test('union of subscript integer three keys followed by member-child-identifier', function() {
  197. var results = jp.nodes(data, "$.store.book[1,2,3]['title']");
  198. assert.deepEqual(results, [
  199. { path: ['$', 'store', 'book', 1, 'title'], value: data.store.book[1].title },
  200. { path: ['$', 'store', 'book', 2, 'title'], value: data.store.book[2].title },
  201. { path: ['$', 'store', 'book', 3, 'title'], value: data.store.book[3].title }
  202. ]);
  203. });
  204. test('union of subscript integer three keys followed by union of subscript string literal three keys', function() {
  205. var results = jp.nodes(data, "$.store.book[0,1,2,3]['title','author','price']");
  206. assert.deepEqual(results, [
  207. { path: ['$', 'store', 'book', 0, 'title'], value: data.store.book[0].title },
  208. { path: ['$', 'store', 'book', 0, 'author'], value: data.store.book[0].author },
  209. { path: ['$', 'store', 'book', 0, 'price'], value: data.store.book[0].price },
  210. { path: ['$', 'store', 'book', 1, 'title'], value: data.store.book[1].title },
  211. { path: ['$', 'store', 'book', 1, 'author'], value: data.store.book[1].author },
  212. { path: ['$', 'store', 'book', 1, 'price'], value: data.store.book[1].price },
  213. { path: ['$', 'store', 'book', 2, 'title'], value: data.store.book[2].title },
  214. { path: ['$', 'store', 'book', 2, 'author'], value: data.store.book[2].author },
  215. { path: ['$', 'store', 'book', 2, 'price'], value: data.store.book[2].price },
  216. { path: ['$', 'store', 'book', 3, 'title'], value: data.store.book[3].title },
  217. { path: ['$', 'store', 'book', 3, 'author'], value: data.store.book[3].author },
  218. { path: ['$', 'store', 'book', 3, 'price'], value: data.store.book[3].price }
  219. ]);
  220. });
  221. test('union of subscript integer four keys, including an inexistent one, followed by union of subscript string literal three keys', function() {
  222. var results = jp.nodes(data, "$.store.book[0,1,2,3,151]['title','author','price']");
  223. assert.deepEqual(results, [
  224. { path: ['$', 'store', 'book', 0, 'title'], value: data.store.book[0].title },
  225. { path: ['$', 'store', 'book', 0, 'author'], value: data.store.book[0].author },
  226. { path: ['$', 'store', 'book', 0, 'price'], value: data.store.book[0].price },
  227. { path: ['$', 'store', 'book', 1, 'title'], value: data.store.book[1].title },
  228. { path: ['$', 'store', 'book', 1, 'author'], value: data.store.book[1].author },
  229. { path: ['$', 'store', 'book', 1, 'price'], value: data.store.book[1].price },
  230. { path: ['$', 'store', 'book', 2, 'title'], value: data.store.book[2].title },
  231. { path: ['$', 'store', 'book', 2, 'author'], value: data.store.book[2].author },
  232. { path: ['$', 'store', 'book', 2, 'price'], value: data.store.book[2].price },
  233. { path: ['$', 'store', 'book', 3, 'title'], value: data.store.book[3].title },
  234. { path: ['$', 'store', 'book', 3, 'author'], value: data.store.book[3].author },
  235. { path: ['$', 'store', 'book', 3, 'price'], value: data.store.book[3].price }
  236. ]);
  237. });
  238. test('union of subscript integer three keys followed by union of subscript string literal three keys, followed by inexistent literal key', function() {
  239. var results = jp.nodes(data, "$.store.book[0,1,2,3]['title','author','price','fruit']");
  240. assert.deepEqual(results, [
  241. { path: ['$', 'store', 'book', 0, 'title'], value: data.store.book[0].title },
  242. { path: ['$', 'store', 'book', 0, 'author'], value: data.store.book[0].author },
  243. { path: ['$', 'store', 'book', 0, 'price'], value: data.store.book[0].price },
  244. { path: ['$', 'store', 'book', 1, 'title'], value: data.store.book[1].title },
  245. { path: ['$', 'store', 'book', 1, 'author'], value: data.store.book[1].author },
  246. { path: ['$', 'store', 'book', 1, 'price'], value: data.store.book[1].price },
  247. { path: ['$', 'store', 'book', 2, 'title'], value: data.store.book[2].title },
  248. { path: ['$', 'store', 'book', 2, 'author'], value: data.store.book[2].author },
  249. { path: ['$', 'store', 'book', 2, 'price'], value: data.store.book[2].price },
  250. { path: ['$', 'store', 'book', 3, 'title'], value: data.store.book[3].title },
  251. { path: ['$', 'store', 'book', 3, 'author'], value: data.store.book[3].author },
  252. { path: ['$', 'store', 'book', 3, 'price'], value: data.store.book[3].price }
  253. ]);
  254. });
  255. test('union of subscript 4 array slices followed by union of subscript string literal three keys', function() {
  256. var results = jp.nodes(data, "$.store.book[0:1,1:2,2:3,3:4]['title','author','price']");
  257. assert.deepEqual(results, [
  258. { path: ['$', 'store', 'book', 0, 'title'], value: data.store.book[0].title },
  259. { path: ['$', 'store', 'book', 0, 'author'], value: data.store.book[0].author },
  260. { path: ['$', 'store', 'book', 0, 'price'], value: data.store.book[0].price },
  261. { path: ['$', 'store', 'book', 1, 'title'], value: data.store.book[1].title },
  262. { path: ['$', 'store', 'book', 1, 'author'], value: data.store.book[1].author },
  263. { path: ['$', 'store', 'book', 1, 'price'], value: data.store.book[1].price },
  264. { path: ['$', 'store', 'book', 2, 'title'], value: data.store.book[2].title },
  265. { path: ['$', 'store', 'book', 2, 'author'], value: data.store.book[2].author },
  266. { path: ['$', 'store', 'book', 2, 'price'], value: data.store.book[2].price },
  267. { path: ['$', 'store', 'book', 3, 'title'], value: data.store.book[3].title },
  268. { path: ['$', 'store', 'book', 3, 'author'], value: data.store.book[3].author },
  269. { path: ['$', 'store', 'book', 3, 'price'], value: data.store.book[3].price }
  270. ]);
  271. });
  272. test('nested parentheses eval', function() {
  273. var pathExpression = '$..book[?( @.price && (@.price + 20 || false) )]'
  274. var results = jp.query(data, pathExpression);
  275. assert.deepEqual(results, data.store.book);
  276. });
  277. test('array indexes from 0 to 100', function() {
  278. var data = [];
  279. for (var i = 0; i <= 100; ++i)
  280. data[i] = Math.random();
  281. for (var i = 0; i <= 100; ++i) {
  282. var results = jp.query(data, '$[' + i.toString() + ']');
  283. assert.deepEqual(results, [data[i]]);
  284. }
  285. });
  286. test('descendant subscript numeric literal', function() {
  287. var data = [ 0, [ 1, 2, 3 ], [ 4, 5, 6 ] ];
  288. var results = jp.query(data, '$..[0]');
  289. assert.deepEqual(results, [ 0, 1, 4 ]);
  290. });
  291. test('descendant subscript numeric literal', function() {
  292. var data = [ 0, 1, [ 2, 3, 4 ], [ 5, 6, 7, [ 8, 9 , 10 ] ] ];
  293. var results = jp.query(data, '$..[0,1]');
  294. assert.deepEqual(results, [ 0, 1, 2, 3, 5, 6, 8, 9 ]);
  295. });
  296. test('throws for no input', function() {
  297. assert.throws(function() { jp.query() }, /needs to be an object/);
  298. });
  299. test('throws for bad input', function() {
  300. assert.throws(function() { jp.query("string", "string") }, /needs to be an object/);
  301. });
  302. test('throws for bad input', function() {
  303. assert.throws(function() { jp.query({}, null) }, /we need a path/);
  304. });
  305. test('throws for bad input', function() {
  306. assert.throws(function() { jp.query({}, 42) }, /we need a path/);
  307. });
  308. test('union on objects', function() {
  309. assert.deepEqual(jp.query({a: 1, b: 2, c: null}, '$..["a","b","c","d"]'), [1, 2, null]);
  310. });
  311. });