Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

README.md 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913
  1. # Object-Scan
  2. [![Build Status](https://circleci.com/gh/blackflux/object-scan.png?style=shield)](https://circleci.com/gh/blackflux/object-scan)
  3. [![Test Coverage](https://img.shields.io/coveralls/blackflux/object-scan/master.svg)](https://coveralls.io/github/blackflux/object-scan?branch=master)
  4. [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=blackflux/object-scan)](https://dependabot.com)
  5. [![Dependencies](https://david-dm.org/blackflux/object-scan/status.svg)](https://david-dm.org/blackflux/object-scan)
  6. [![NPM](https://img.shields.io/npm/v/object-scan.svg)](https://www.npmjs.com/package/object-scan)
  7. [![Downloads](https://img.shields.io/npm/dt/object-scan.svg)](https://www.npmjs.com/package/object-scan)
  8. [![Semantic-Release](https://github.com/blackflux/js-gardener/blob/master/assets/icons/semver.svg)](https://github.com/semantic-release/semantic-release)
  9. [![Gardener](https://github.com/blackflux/js-gardener/blob/master/assets/badge.svg)](https://github.com/blackflux/js-gardener)
  10. Traverse object hierarchies using matching and callbacks.
  11. ## Install
  12. Install with [npm](https://www.npmjs.com/):
  13. $ npm install --save object-scan
  14. ## Usage
  15. <!-- eslint-disable-next-line import/no-unresolved, import/no-extraneous-dependencies -->
  16. ```js
  17. const objectScan = require('object-scan');
  18. const haystack = { a: { b: { c: 'd' }, e: { f: 'g' } } };
  19. objectScan(['a.*.f'], { joined: true })(haystack);
  20. // => [ 'a.e.f' ]
  21. ```
  22. ## Features
  23. - Input traversed exactly once during search
  24. - Dependency free, small in size and very performant
  25. - Separate Object and Array matching
  26. - Wildcard and Regex matching
  27. - Arbitrary depth matching
  28. - Or-clause Syntax
  29. - Exclusion Matching
  30. - Full support for escaping
  31. - Traversal in "delete-safe" order
  32. - Recursion free implementation
  33. - Search syntax validated
  34. - Lots of tests and examples
  35. ## Matching
  36. A needle expression specifies one or more paths to an element (or a set of elements) in a JSON structure. Paths use the dot notation:
  37. ```txt
  38. store.book[0].title
  39. ```
  40. ### Array
  41. Rectangular brackets for array path matching.
  42. _Examples_:
  43. <details><summary> <code>['[2]']</code> <em>(exact in array)</em> </summary>
  44. <!-- eslint-disable no-undef -->
  45. ```js
  46. const haystack = [0, 1, 2, 3, 4];
  47. objectScan(['[2]'], { joined: true })(haystack);
  48. // => [ '[2]' ]
  49. ```
  50. </details>
  51. <details><summary> <code>['[1]']</code> <em>(no match in object)</em> </summary>
  52. <!-- eslint-disable no-undef -->
  53. ```js
  54. const haystack = { 0: 'a', 1: 'b', 2: 'c' };
  55. objectScan(['[1]'], { joined: true })(haystack);
  56. // => []
  57. ```
  58. </details>
  59. ### Object
  60. Property name for object property matching.
  61. _Examples_:
  62. <details><summary> <code>['foo']</code> <em>(exact in object)</em> </summary>
  63. <!-- eslint-disable no-undef -->
  64. ```js
  65. const haystack = { foo: 0, bar: 1 };
  66. objectScan(['foo'], { joined: true })(haystack);
  67. // => [ 'foo' ]
  68. ```
  69. </details>
  70. <details><summary> <code>['1']</code> <em>(no match in array)</em> </summary>
  71. <!-- eslint-disable no-undef -->
  72. ```js
  73. const haystack = [0, 1, 2, 3, 4];
  74. objectScan(['1'], { joined: true })(haystack);
  75. // => []
  76. ```
  77. </details>
  78. ### Wildcard
  79. The following characters have special meaning when not escaped:
  80. - `*`: Match zero or more character
  81. - `+`: Match one or more character
  82. - `?`: Match exactly one character
  83. - `\`: Escape the subsequent character
  84. Wildcards can be used with Array and Object selector.
  85. _Examples_:
  86. <details><summary> <code>['*']</code> <em>(top level)</em> </summary>
  87. <!-- eslint-disable no-undef -->
  88. ```js
  89. const haystack = { a: { b: 0, c: 1 }, d: 2 };
  90. objectScan(['*'], { joined: true })(haystack);
  91. // => [ 'd', 'a' ]
  92. ```
  93. </details>
  94. <details><summary> <code>['[?5]']</code> <em>(two digit ending in five)</em> </summary>
  95. <!-- eslint-disable no-undef -->
  96. ```js
  97. const haystack = [...Array(30).keys()];
  98. objectScan(['[?5]'], { joined: true })(haystack);
  99. // => [ '[25]', '[15]' ]
  100. ```
  101. </details>
  102. <details><summary> <code>['a.+.c']</code> <em>(nested)</em> </summary>
  103. <!-- eslint-disable no-undef -->
  104. ```js
  105. const haystack = { a: { b: { c: 0 }, d: { f: 0 } } };
  106. objectScan(['a.+.c'], { joined: true })(haystack);
  107. // => [ 'a.b.c' ]
  108. ```
  109. </details>
  110. <details><summary> <code>['a.\\+.c']</code> <em>(escaped)</em> </summary>
  111. <!-- eslint-disable no-undef -->
  112. ```js
  113. const haystack = { a: { b: { c: 0 }, '+': { c: 0 } } };
  114. objectScan(['a.\\+.c'], { joined: true })(haystack);
  115. // => [ 'a.\\+.c' ]
  116. ```
  117. </details>
  118. ### Regex
  119. Regex are defined by using parentheses.
  120. Can be used with Array and Object selector.
  121. _Examples_:
  122. <details><summary> <code>['(^foo)']</code> <em>(starting with `foo`)</em> </summary>
  123. <!-- eslint-disable no-undef -->
  124. ```js
  125. const haystack = { foo: 0, foobar: 1, bar: 2 };
  126. objectScan(['(^foo)'], { joined: true })(haystack);
  127. // => [ 'foobar', 'foo' ]
  128. ```
  129. </details>
  130. <details><summary> <code>['[(5)]']</code> <em>(containing `5`)</em> </summary>
  131. <!-- eslint-disable no-undef -->
  132. ```js
  133. const haystack = [...Array(20).keys()];
  134. objectScan(['[(5)]'], { joined: true })(haystack);
  135. // => [ '[15]', '[5]' ]
  136. ```
  137. </details>
  138. <details><summary> <code>['[(^[01]$)]']</code> <em>(`[0]` and `[1]`)</em> </summary>
  139. <!-- eslint-disable no-undef -->
  140. ```js
  141. const haystack = ['a', 'b', 'c', 'd'];
  142. objectScan(['[(^[01]$)]'], { joined: true })(haystack);
  143. // => [ '[1]', '[0]' ]
  144. ```
  145. </details>
  146. <details><summary> <code>['[(^[^01]$)]']</code> <em>(other than `[0]` and `[1]`)</em> </summary>
  147. <!-- eslint-disable no-undef -->
  148. ```js
  149. const haystack = ['a', 'b', 'c', 'd'];
  150. objectScan(['[(^[^01]$)]'], { joined: true })(haystack);
  151. // => [ '[3]', '[2]' ]
  152. ```
  153. </details>
  154. <details><summary> <code>['[*]', '[!(^[01]$)]']</code> <em>(match all and exclude `[0]` and `[1]`)</em> </summary>
  155. <!-- eslint-disable no-undef -->
  156. ```js
  157. const haystack = ['a', 'b', 'c', 'd'];
  158. objectScan(['[*]', '[!(^[01]$)]'], { joined: true })(haystack);
  159. // => [ '[3]', '[2]' ]
  160. ```
  161. </details>
  162. ### Arbitrary Depth
  163. There are two types of arbitrary depth matching:
  164. - `**`: Matches zero or more nestings
  165. - `++`: Matches one or more nestings
  166. Recursions can be combined with a regex by appending the regex.
  167. _Examples_:
  168. <details><summary> <code>['a.**']</code> <em>(zero or more nestings under `a`)</em> </summary>
  169. <!-- eslint-disable no-undef -->
  170. ```js
  171. const haystack = { a: { b: 0, c: 0 } };
  172. objectScan(['a.**'], { joined: true })(haystack);
  173. // => [ 'a.c', 'a.b', 'a' ]
  174. ```
  175. </details>
  176. <details><summary> <code>['a.++']</code> <em>(one or more nestings under `a`)</em> </summary>
  177. <!-- eslint-disable no-undef -->
  178. ```js
  179. const haystack = { a: { b: 0, c: 0 } };
  180. objectScan(['a.++'], { joined: true })(haystack);
  181. // => [ 'a.c', 'a.b' ]
  182. ```
  183. </details>
  184. <details><summary> <code>['**(1)']</code> <em>(all containing `1` at every level)</em> </summary>
  185. <!-- eslint-disable no-undef -->
  186. ```js
  187. const haystack = { 1: { 1: ['c', 'd'] }, 510: 'e', foo: { 1: 'f' } };
  188. objectScan(['**(1)'], { joined: true })(haystack);
  189. // => [ '510', '1.1[1]', '1.1', '1' ]
  190. ```
  191. </details>
  192. ### Or Clause
  193. Or Clauses are defined by using curley brackets.
  194. Can be used with Array and Object selector.
  195. _Examples_:
  196. <details><summary> <code>['[{0,1}]']</code> <em>(`[0]` and `[1]`)</em> </summary>
  197. <!-- eslint-disable no-undef -->
  198. ```js
  199. const haystack = ['a', 'b', 'c', 'd'];
  200. objectScan(['[{0,1}]'], { joined: true })(haystack);
  201. // => [ '[1]', '[0]' ]
  202. ```
  203. </details>
  204. <details><summary> <code>['{a,d}.{b,f}']</code> <em>(`a.b`, `a.f`, `d.b` and `d.f`)</em> </summary>
  205. <!-- eslint-disable no-undef -->
  206. ```js
  207. const haystack = { a: { b: 0, c: 1 }, d: { e: 2, f: 3 } };
  208. objectScan(['{a,d}.{b,f}'], { joined: true })(haystack);
  209. // => [ 'd.f', 'a.b' ]
  210. ```
  211. </details>
  212. ### Exclusion
  213. To exclude a path, use exclamation mark.
  214. _Examples_:
  215. <details><summary> <code>['{a,b},!a']</code> <em>(only `b`)</em> </summary>
  216. <!-- eslint-disable no-undef -->
  217. ```js
  218. const haystack = { a: 0, b: 1 };
  219. objectScan(['{a,b},!a'], {
  220. joined: true,
  221. strict: false
  222. })(haystack);
  223. // => [ 'b' ]
  224. ```
  225. </details>
  226. <details><summary> <code>['**,!**.a']</code> <em>(all except ending in `a`)</em> </summary>
  227. <!-- eslint-disable no-undef -->
  228. ```js
  229. const haystack = { a: 0, b: { a: 1, c: 2 } };
  230. objectScan(['**,!**.a'], { joined: true })(haystack);
  231. // => [ 'b.c', 'b' ]
  232. ```
  233. </details>
  234. ### Escaping
  235. The following characters are considered special and need to
  236. be escaped using `\`, if they should be matched in a key:<br>
  237. `[`, `]`, `{`, `}`, `(`, `)`, `,`, `.`, `!`, `?`, `*`, `+` and `\`.
  238. _Examples:_
  239. <details><summary> <code>['\\[1\\]']</code> <em>(special object key)</em> </summary>
  240. <!-- eslint-disable no-undef -->
  241. ```js
  242. const haystack = { '[1]': 0 };
  243. objectScan(['\\[1\\]'], { joined: true })(haystack);
  244. // => [ '\\[1\\]' ]
  245. ```
  246. </details>
  247. ## Options
  248. Signature of all callbacks is
  249. Fn({ key, value, ... })
  250. where:
  251. - `key`: key that callback is invoked for (respects `joined` option).
  252. - `value`: value for key.
  253. - `entry`: entry consisting of [`key`, `value`].
  254. - `property`: current parent property.
  255. - `parent`: current parent.
  256. - `parents`: array of form `[parent, grandparent, ...]`.
  257. - `isMatch`: true iff last targeting needle exists and is non-excluding.
  258. - `matchedBy`: all non-excluding needles targeting key.
  259. - `excludedBy`: all excluding needles targeting key.
  260. - `traversedBy`: all needles involved in traversing key.
  261. - `isCircular`: true iff `value` contained in `parents`
  262. - `isLeaf`: true iff `value` can not be traversed
  263. - `getKey`: function that returns `key`
  264. - `getValue`: function that returns `value`
  265. - `getEntry`: function that returns `entry`
  266. - `getProperty`: function that returns `property`
  267. - `getParent`: function that returns `parent`
  268. - `getParents`: function that returns `parents`
  269. - `getIsMatch`: function that returns `isMatch`
  270. - `getMatchedBy`: function that returns `matchedBy`
  271. - `getExcludedBy`: function that returns `excludedBy`
  272. - `getTraversedBy`: function that returns `traversedBy`
  273. - `getIsCircular`: function that returns `isCircular`
  274. - `getIsLeaf`: function that returns `isLeaf`
  275. - `context`: as passed into the search
  276. Notes on Performance:
  277. - Arguments backed by getters use [Functions Getter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)
  278. and should be accessed via [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Unpacking_fields_from_objects_passed_as_function_parameter) to prevent redundant computation.
  279. - Getters should be used to improve performance for conditional access. E.g. `if (isMatch) { getParents() ... }`.
  280. - For performance reasons, the same object is passed to all callbacks.
  281. #### filterFn
  282. Type: `function`<br>
  283. Default: `undefined`
  284. When defined, this callback is invoked for every match. If `false`
  285. is returned, the current key is excluded from the result.
  286. The return value of this callback has no effect when a search context is provided.
  287. Can be used to do processing as matching keys are traversed.
  288. Invoked in same order as matches would appear in result.
  289. This method is conceptually similar to
  290. [Array.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter).
  291. _Examples_:
  292. <details><summary> <code>['**']</code> <em>(filter function)</em> </summary>
  293. <!-- eslint-disable no-undef -->
  294. ```js
  295. const haystack = { a: 0, b: 'bar' };
  296. objectScan(['**'], {
  297. joined: true,
  298. filterFn: ({ value }) => typeof value === 'string'
  299. })(haystack);
  300. // => [ 'b' ]
  301. ```
  302. </details>
  303. #### breakFn
  304. Type: `function`<br>
  305. Default: `undefined`
  306. When defined, this callback is invoked for every key that is traversed by
  307. the search. If `true` is returned, all keys nested under the current key are
  308. skipped in the search and from the final result.
  309. Note that `breakFn` is invoked before the corresponding `filterFn` might be invoked.
  310. _Examples_:
  311. <details><summary> <code>['**']</code> <em>(break function)</em> </summary>
  312. <!-- eslint-disable no-undef -->
  313. ```js
  314. const haystack = { a: { b: { c: 0 } } };
  315. objectScan(['**'], {
  316. joined: true,
  317. breakFn: ({ key }) => key === 'a.b'
  318. })(haystack);
  319. // => [ 'a.b', 'a' ]
  320. ```
  321. </details>
  322. #### compareFn
  323. Type: `function`<br>
  324. Default: `undefined`
  325. When defined, this function is used as a comparator to determine the traversal order of any `object` keys.
  326. This works together with the `reverse` option.
  327. _Examples_:
  328. <details><summary> <code>['**']</code> <em>(simple sort)</em> </summary>
  329. <!-- eslint-disable no-undef -->
  330. ```js
  331. const haystack = { a: 0, c: 1, b: 2 };
  332. objectScan(['**'], {
  333. joined: true,
  334. compareFn: (k1, k2) => k1.localeCompare(k2),
  335. reverse: false
  336. })(haystack);
  337. // => [ 'a', 'b', 'c' ]
  338. ```
  339. </details>
  340. #### reverse
  341. Type: `boolean`<br>
  342. Default: `true`
  343. When set to `true`, the scan is performed in reverse order. This means `breakFn` is executed in _reverse post-order_ and
  344. `filterFn` in _reverse pre-order_. Otherwise `breakFn` is executed in _pre-order_ and `filterFn` in _post-order_.
  345. When `reverse` is `true` the scan is _delete-safe_. I.e. `property` can be deleted / spliced from `parent` object / array in `filterFn`.
  346. _Examples_:
  347. <details><summary> <code>['**']</code> <em>(breakFn, reverse true)</em> </summary>
  348. <!-- eslint-disable no-undef -->
  349. ```js
  350. const haystack = { f: { b: { a: {}, d: { c: {}, e: {} } }, g: { i: { h: {} } } } };
  351. objectScan(['**'], {
  352. breakFn: ({ isMatch, property, context }) => { if (isMatch) { context.push(property); } },
  353. reverse: true
  354. })(haystack, []);
  355. // => [ 'f', 'g', 'i', 'h', 'b', 'd', 'e', 'c', 'a' ]
  356. ```
  357. </details>
  358. <details><summary> <code>['**']</code> <em>(filterFn, reverse true)</em> </summary>
  359. <!-- eslint-disable no-undef -->
  360. ```js
  361. const haystack = { f: { b: { a: {}, d: { c: {}, e: {} } }, g: { i: { h: {} } } } };
  362. objectScan(['**'], {
  363. filterFn: ({ property, context }) => { context.push(property); },
  364. reverse: true
  365. })(haystack, []);
  366. // => [ 'h', 'i', 'g', 'e', 'c', 'd', 'a', 'b', 'f' ]
  367. ```
  368. </details>
  369. <details><summary> <code>['**']</code> <em>(breakFn, reverse false)</em> </summary>
  370. <!-- eslint-disable no-undef -->
  371. ```js
  372. const haystack = { f: { b: { a: {}, d: { c: {}, e: {} } }, g: { i: { h: {} } } } };
  373. objectScan(['**'], {
  374. breakFn: ({ isMatch, property, context }) => { if (isMatch) { context.push(property); } },
  375. reverse: false
  376. })(haystack, []);
  377. // => [ 'f', 'b', 'a', 'd', 'c', 'e', 'g', 'i', 'h' ]
  378. ```
  379. </details>
  380. <details><summary> <code>['**']</code> <em>(filterFn, reverse false)</em> </summary>
  381. <!-- eslint-disable no-undef -->
  382. ```js
  383. const haystack = { f: { b: { a: {}, d: { c: {}, e: {} } }, g: { i: { h: {} } } } };
  384. objectScan(['**'], {
  385. filterFn: ({ property, context }) => { context.push(property); },
  386. reverse: false
  387. })(haystack, []);
  388. // => [ 'a', 'c', 'e', 'd', 'b', 'h', 'i', 'g', 'f' ]
  389. ```
  390. </details>
  391. #### abort
  392. Type: `boolean`<br>
  393. Default: `false`
  394. When set to `true` the scan immediately returns after the first match.
  395. _Examples_:
  396. <details><summary> <code>['a', 'b']</code> <em>(only return first property)</em> </summary>
  397. <!-- eslint-disable no-undef -->
  398. ```js
  399. const haystack = { a: 0, b: 1 };
  400. objectScan(['a', 'b'], {
  401. rtn: 'property',
  402. abort: true
  403. })(haystack);
  404. // => 'b'
  405. ```
  406. </details>
  407. <details><summary> <code>['[0]', '[1]']</code> <em>(abort changes count)</em> </summary>
  408. <!-- eslint-disable no-undef -->
  409. ```js
  410. const haystack = ['a', 'b'];
  411. objectScan(['[0]', '[1]'], {
  412. rtn: 'count',
  413. abort: true
  414. })(haystack);
  415. // => 1
  416. ```
  417. </details>
  418. #### rtn
  419. Type: `string`<br>
  420. Default: _dynamic_
  421. Defaults to `key` when search context is _undefined_ and to `context` otherwise.
  422. Can be explicitly set as:
  423. - `context`: search context is returned
  424. - `key`: matched keys are returned
  425. - `value`: matched values are returned
  426. - `entry`: matched entries are returned
  427. - `property`: matched properties are returned
  428. - `parent`: matched parent are returned
  429. - `parents`: matched parents are returned
  430. - `bool`: returns _true_ iff a match is found
  431. - `count`: returns the match count
  432. When **abort** is set to `true` and the result would be a list, the first match or _undefined_ is returned.
  433. _Examples_:
  434. <details><summary> <code>['[*]']</code> <em>(return values)</em> </summary>
  435. <!-- eslint-disable no-undef -->
  436. ```js
  437. const haystack = ['a', 'b', 'c'];
  438. objectScan(['[*]'], { rtn: 'value' })(haystack);
  439. // => [ 'c', 'b', 'a' ]
  440. ```
  441. </details>
  442. <details><summary> <code>['foo[*]']</code> <em>(return entries)</em> </summary>
  443. <!-- eslint-disable no-undef -->
  444. ```js
  445. const haystack = { foo: ['bar'] };
  446. objectScan(['foo[*]'], { rtn: 'entry' })(haystack);
  447. // => [ [ [ 'foo', 0 ], 'bar' ] ]
  448. ```
  449. </details>
  450. <details><summary> <code>['a.b.c', 'a']</code> <em>(return properties)</em> </summary>
  451. <!-- eslint-disable no-undef -->
  452. ```js
  453. const haystack = { a: { b: { c: 0 } } };
  454. objectScan(['a.b.c', 'a'], { rtn: 'property' })(haystack);
  455. // => [ 'c', 'a' ]
  456. ```
  457. </details>
  458. <details><summary> <code>['a.b', 'a.c']</code> <em>(checks for any match, full scan)</em> </summary>
  459. <!-- eslint-disable no-undef -->
  460. ```js
  461. const haystack = { a: { b: 0, c: 1 } };
  462. objectScan(['a.b', 'a.c'], { rtn: 'bool' })(haystack);
  463. // => true
  464. ```
  465. </details>
  466. <details><summary> <code>['**']</code> <em>(return not provided context)</em> </summary>
  467. <!-- eslint-disable no-undef -->
  468. ```js
  469. const haystack = { a: 0 };
  470. objectScan(['**'], { rtn: 'context' })(haystack);
  471. // => undefined
  472. ```
  473. </details>
  474. <details><summary> <code>['a.b.{c,d}']</code> <em>(return keys with context passed)</em> </summary>
  475. <!-- eslint-disable no-undef -->
  476. ```js
  477. const haystack = { a: { b: { c: 0, d: 1 } } };
  478. objectScan(['a.b.{c,d}'], { rtn: 'key' })(haystack, []);
  479. // => [ [ 'a', 'b', 'd' ], [ 'a', 'b', 'c' ] ]
  480. ```
  481. </details>
  482. #### joined
  483. Type: `boolean`<br>
  484. Default: `false`
  485. Keys are returned as a string when set to `true` instead of as a list.
  486. Setting this option to `true` will negatively impact performance.
  487. Note that [_.get](https://lodash.com/docs/#get) and [_.set](https://lodash.com/docs/#set) fully support lists.
  488. _Examples_:
  489. <details><summary> <code>['[*]', '[*].foo']</code> <em>(joined)</em> </summary>
  490. <!-- eslint-disable no-undef -->
  491. ```js
  492. const haystack = [0, 1, { foo: 'bar' }];
  493. objectScan(['[*]', '[*].foo'], { joined: true })(haystack);
  494. // => [ '[2].foo', '[2]', '[1]', '[0]' ]
  495. ```
  496. </details>
  497. <details><summary> <code>['[*]', '[*].foo']</code> <em>(not joined)</em> </summary>
  498. <!-- eslint-disable no-undef -->
  499. ```js
  500. const haystack = [0, 1, { foo: 'bar' }];
  501. objectScan(['[*]', '[*].foo'])(haystack);
  502. // => [ [ 2, 'foo' ], [ 2 ], [ 1 ], [ 0 ] ]
  503. ```
  504. </details>
  505. #### useArraySelector
  506. Type: `boolean`<br>
  507. Default: `true`
  508. When set to `false`, no array selectors should be used in any needles and arrays are automatically traversed.
  509. Note that the results still include the array selectors.
  510. _Examples_:
  511. <details><summary> <code>['a', 'b.d']</code> <em>(automatic array traversal)</em> </summary>
  512. <!-- eslint-disable no-undef -->
  513. ```js
  514. const haystack = [{ a: 0 }, { b: [{ c: 1 }, { d: 2 }] }];
  515. objectScan(['a', 'b.d'], {
  516. joined: true,
  517. useArraySelector: false
  518. })(haystack);
  519. // => [ '[1].b[1].d', '[0].a' ]
  520. ```
  521. </details>
  522. <details><summary> <code>['']</code> <em>(top level array matching)</em> </summary>
  523. <!-- eslint-disable no-undef -->
  524. ```js
  525. const haystack = [{ a: 0 }, { b: 1 }];
  526. objectScan([''], {
  527. joined: true,
  528. useArraySelector: false
  529. })(haystack);
  530. // => [ '[1]', '[0]' ]
  531. ```
  532. </details>
  533. #### strict
  534. Type: `boolean`<br>
  535. Default: `true`
  536. When set to `true`, errors are thrown when:
  537. - a path is identical to a previous path
  538. - a path invalidates a previous path
  539. - a path contains consecutive recursions
  540. _Examples_:
  541. <details><summary> <code>['a.b', 'a.b']</code> <em>(identical)</em> </summary>
  542. <!-- eslint-disable no-undef -->
  543. ```js
  544. const haystack = [];
  545. objectScan(['a.b', 'a.b'], { joined: true })(haystack);
  546. // => 'Error: Redundant Needle Target: "a.b" vs "a.b"'
  547. ```
  548. </details>
  549. <details><summary> <code>['a.{b,b}']</code> <em>(identical, same needle)</em> </summary>
  550. <!-- eslint-disable no-undef -->
  551. ```js
  552. const haystack = [];
  553. objectScan(['a.{b,b}'], { joined: true })(haystack);
  554. // => 'Error: Redundant Needle Target: "a.{b,b}" vs "a.{b,b}"'
  555. ```
  556. </details>
  557. <details><summary> <code>['a.b', 'a.**']</code> <em>(invalidates previous)</em> </summary>
  558. <!-- eslint-disable no-undef -->
  559. ```js
  560. const haystack = [];
  561. objectScan(['a.b', 'a.**'], { joined: true })(haystack);
  562. // => 'Error: Needle Target Invalidated: "a.b" by "a.**"'
  563. ```
  564. </details>
  565. <details><summary> <code>['**.!**']</code> <em>(consecutive recursion)</em> </summary>
  566. <!-- eslint-disable no-undef -->
  567. ```js
  568. const haystack = [];
  569. objectScan(['**.!**'], { joined: true })(haystack);
  570. // => 'Error: Redundant Recursion: "**.!**"'
  571. ```
  572. </details>
  573. ### Search Context
  574. A context can be passed into a search invocation as a second parameter. It is available in all callbacks
  575. and can be used to manage state across a search invocation without having to recompile the search.
  576. By default all matched keys are returned from a search invocation.
  577. However, when it is not _undefined_, the context is returned instead.
  578. _Examples_:
  579. <details><summary> <code>['**.{c,d,e}']</code> <em>(sum values)</em> </summary>
  580. <!-- eslint-disable no-undef -->
  581. ```js
  582. const haystack = { a: { b: { c: 2, d: 11 }, e: 7 } };
  583. objectScan(['**.{c,d,e}'], {
  584. joined: true,
  585. filterFn: ({ value, context }) => { context.sum += value; }
  586. })(haystack, { sum: 0 });
  587. // => { sum: 20 }
  588. ```
  589. </details>
  590. ## Examples
  591. More extensive examples can be found in the tests.
  592. <details><summary> <code>['a.*.f']</code> <em>(nested)</em> </summary>
  593. <!-- eslint-disable no-undef -->
  594. ```js
  595. const haystack = { a: { b: { c: 'd' }, e: { f: 'g' }, h: ['i', 'j'] }, k: 'l' };
  596. objectScan(['a.*.f'], { joined: true })(haystack);
  597. // => [ 'a.e.f' ]
  598. ```
  599. </details>
  600. <details><summary> <code>['*.*.*']</code> <em>(multiple nested)</em> </summary>
  601. <!-- eslint-disable no-undef -->
  602. ```js
  603. const haystack = { a: { b: { c: 'd' }, e: { f: 'g' }, h: ['i', 'j'] }, k: 'l' };
  604. objectScan(['*.*.*'], { joined: true })(haystack);
  605. // => [ 'a.e.f', 'a.b.c' ]
  606. ```
  607. </details>
  608. <details><summary> <code>['a.*.{c,f}']</code> <em>(or filter)</em> </summary>
  609. <!-- eslint-disable no-undef -->
  610. ```js
  611. const haystack = { a: { b: { c: 'd' }, e: { f: 'g' }, h: ['i', 'j'] }, k: 'l' };
  612. objectScan(['a.*.{c,f}'], { joined: true })(haystack);
  613. // => [ 'a.e.f', 'a.b.c' ]
  614. ```
  615. </details>
  616. <details><summary> <code>['a.*.{c,f}']</code> <em>(or filter, not joined)</em> </summary>
  617. <!-- eslint-disable no-undef -->
  618. ```js
  619. const haystack = { a: { b: { c: 'd' }, e: { f: 'g' }, h: ['i', 'j'] }, k: 'l' };
  620. objectScan(['a.*.{c,f}'])(haystack);
  621. // => [ [ 'a', 'e', 'f' ], [ 'a', 'b', 'c' ] ]
  622. ```
  623. </details>
  624. <details><summary> <code>['*.*[*]']</code> <em>(list filter)</em> </summary>
  625. <!-- eslint-disable no-undef -->
  626. ```js
  627. const haystack = { a: { b: { c: 'd' }, e: { f: 'g' }, h: ['i', 'j'] }, k: 'l' };
  628. objectScan(['*.*[*]'], { joined: true })(haystack);
  629. // => [ 'a.h[1]', 'a.h[0]' ]
  630. ```
  631. </details>
  632. <details><summary> <code>['*[*]']</code> <em>(list filter, unmatched)</em> </summary>
  633. <!-- eslint-disable no-undef -->
  634. ```js
  635. const haystack = { a: { b: { c: 'd' }, e: { f: 'g' }, h: ['i', 'j'] }, k: 'l' };
  636. objectScan(['*[*]'], { joined: true })(haystack);
  637. // => []
  638. ```
  639. </details>
  640. <details><summary> <code>['**']</code> <em>(star recursion)</em> </summary>
  641. <!-- eslint-disable no-undef -->
  642. ```js
  643. const haystack = { a: { b: { c: 'd' }, e: { f: 'g' }, h: ['i', 'j'] }, k: 'l' };
  644. objectScan(['**'], { joined: true })(haystack);
  645. // => [ 'k', 'a.h[1]', 'a.h[0]', 'a.h', 'a.e.f', 'a.e', 'a.b.c', 'a.b', 'a' ]
  646. ```
  647. </details>
  648. <details><summary> <code>['++.++']</code> <em>(plus recursion)</em> </summary>
  649. <!-- eslint-disable no-undef -->
  650. ```js
  651. const haystack = { a: { b: { c: 'd' }, e: { f: 'g' }, h: ['i', 'j'] }, k: 'l' };
  652. objectScan(['++.++'], { joined: true })(haystack);
  653. // => [ 'a.h[1]', 'a.h[0]', 'a.h', 'a.e.f', 'a.e', 'a.b.c', 'a.b' ]
  654. ```
  655. </details>
  656. <details><summary> <code>['**.f']</code> <em>(star recursion ending in f)</em> </summary>
  657. <!-- eslint-disable no-undef -->
  658. ```js
  659. const haystack = { a: { b: { c: 'd' }, e: { f: 'g' }, h: ['i', 'j'] }, k: 'l' };
  660. objectScan(['**.f'], { joined: true })(haystack);
  661. // => [ 'a.e.f' ]
  662. ```
  663. </details>
  664. <details><summary> <code>['**[*]']</code> <em>(star recursion ending in array)</em> </summary>
  665. <!-- eslint-disable no-undef -->
  666. ```js
  667. const haystack = { a: { b: { c: 'd' }, e: { f: 'g' }, h: ['i', 'j'] }, k: 'l' };
  668. objectScan(['**[*]'], { joined: true })(haystack);
  669. // => [ 'a.h[1]', 'a.h[0]' ]
  670. ```
  671. </details>
  672. <details><summary> <code>['a.*,!a.e']</code> <em>(exclusion filter)</em> </summary>
  673. <!-- eslint-disable no-undef -->
  674. ```js
  675. const haystack = { a: { b: { c: 'd' }, e: { f: 'g' }, h: ['i', 'j'] }, k: 'l' };
  676. objectScan(['a.*,!a.e'], { joined: true })(haystack);
  677. // => [ 'a.h', 'a.b' ]
  678. ```
  679. </details>
  680. <details><summary> <code>['**.(^[bc]$)']</code> <em>(regex matching)</em> </summary>
  681. <!-- eslint-disable no-undef -->
  682. ```js
  683. const haystack = { a: { b: { c: 'd' }, e: { f: 'g' }, h: ['i', 'j'] }, k: 'l' };
  684. objectScan(['**.(^[bc]$)'], { joined: true })(haystack);
  685. // => [ 'a.b.c', 'a.b' ]
  686. ```
  687. </details>
  688. ## Edge Cases
  689. Top level object(s) are matched by the empty needle `''`. This is useful for matching objects nested in arrays by setting `useArraySelector` to `false`.
  690. To match the actual empty string as a key, use `(^$)`.
  691. Note that the empty string does not work to match top level objects with
  692. [_.get](https://lodash.com/docs/#get) or [_.set](https://lodash.com/docs/#set).
  693. _Examples_:
  694. <details><summary> <code>['']</code> <em>(match top level objects in array)</em> </summary>
  695. <!-- eslint-disable no-undef -->
  696. ```js
  697. const haystack = [{}, {}];
  698. objectScan([''], {
  699. joined: true,
  700. useArraySelector: false
  701. })(haystack);
  702. // => [ '[1]', '[0]' ]
  703. ```
  704. </details>
  705. <details><summary> <code>['']</code> <em>(match top level object)</em> </summary>
  706. <!-- eslint-disable no-undef -->
  707. ```js
  708. const haystack = {};
  709. objectScan([''], { joined: true })(haystack);
  710. // => [ '' ]
  711. ```
  712. </details>
  713. <details><summary> <code>['**.(^$)']</code> <em>(match empty string keys)</em> </summary>
  714. <!-- eslint-disable no-undef -->
  715. ```js
  716. const haystack = { '': 0, a: { '': 1 } };
  717. objectScan(['**.(^$)'])(haystack);
  718. // => [ [ 'a', '' ], [ '' ] ]
  719. ```
  720. </details>
  721. ## Internals
  722. Conceptually this package works as follows:
  723. 1. During initialization the needles are parsed and built into a search tree.
  724. Various information is pre-computed and stored for every node.
  725. Finally the search function is returned.
  726. 2. When the search function is invoked, the input is traversed simultaneously with
  727. the relevant nodes of the search tree. Processing multiple search tree branches
  728. in parallel allows for a single traversal of the input.
  729. Having a separate initialization stage allows for a performant search and
  730. significant speed ups when applying the same search to different input.