├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── nodejs.yml │ └── npmpublish.yml ├── .gitignore ├── .lintstagedrc.json ├── .nojekyll ├── .prettierignore ├── .prettierrc.js ├── LICENSE ├── LODASH_SUPPORT.md ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── rollup.config.ts ├── scripts ├── gh-pages-publish.js ├── jp.js └── perf.js ├── src ├── index.ts ├── plugins.ts ├── supportedFunctions.ts └── utils │ └── number-scale.ts ├── test ├── compliance.spec.js ├── compliance │ ├── basic.json │ ├── boolean.json │ ├── current.json │ ├── escape.json │ ├── filters.json │ ├── functions.json │ ├── identifiers.json │ ├── indices.json │ ├── literal.json │ ├── multiselect.json │ ├── pipe.json │ ├── slice.json │ ├── syntax.json │ ├── unicode.json │ └── wildcard.json ├── jmespath.spec.ts ├── lodash │ ├── array.spec.ts │ ├── boilerplate.spec.ts │ ├── collection.spec.ts │ ├── lang.spec.ts │ ├── math.spec.ts │ ├── number.spec.ts │ ├── object.spec.ts │ ├── string.spec.ts │ └── util.spec.ts └── plugins.spec.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | # We recommend you to keep these unchanged 9 | charset = utf-8 10 | end_of_line = lf 11 | indent_size = 2 12 | indent_style = space 13 | insert_final_newline = true 14 | # editorconfig-tools is unable to ignore longs strings or urls 15 | max_line_length = null 16 | trim_trailing_whitespace = true 17 | 18 | [*.md] 19 | insert_final_newline = false 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | jest.config.js 4 | fixtures 5 | shared-fixtures 6 | coverage 7 | __snapshots__ 8 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // src has additional rules at src/.eslintrc.js 2 | module.exports = { 3 | root: true, 4 | parser: '@typescript-eslint/parser', 5 | parserOptions: { 6 | sourceType: 'module', 7 | ecmaVersion: 2020, 8 | }, 9 | plugins: ['@typescript-eslint', 'prettier'], 10 | extends: [ 11 | 'plugin:@typescript-eslint/recommended', 12 | 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 13 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 14 | ], 15 | env: { 16 | browser: true, 17 | commonjs: true, 18 | node: true, 19 | mocha: true, 20 | es6: true, 21 | }, 22 | rules: { 23 | curly: 'error', 24 | '@typescript-eslint/camelcase': 'off', 25 | '@typescript-eslint/naming-convention': [ 26 | 'error', 27 | { selector: 'variable', format: ['camelCase', 'UPPER_CASE', 'PascalCase'] }, 28 | ], 29 | '@typescript-eslint/ban-ts-comment': 'off', 30 | '@typescript-eslint/class-name-casing': 'off', 31 | '@typescript-eslint/no-empty-function': 'off', 32 | 'prettier/prettier': 'error', 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [10.x, 12.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm ci 28 | - run: npm run build --if-present 29 | - run: npm test 30 | -------------------------------------------------------------------------------- /.github/workflows/npmpublish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: 12 18 | - run: npm ci 19 | - run: npm test 20 | 21 | publish-npm: 22 | needs: test 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions/setup-node@v1 27 | with: 28 | node-version: 12 29 | registry-url: https://registry.npmjs.org/ 30 | - run: npm ci 31 | - run: npm run build 32 | - run: npm publish --verbose --access public --tag latest 33 | env: 34 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 35 | 36 | # publish-gpr: 37 | # needs: build 38 | # runs-on: ubuntu-latest 39 | # steps: 40 | # - uses: actions/checkout@v2 41 | # - uses: actions/setup-node@v1 42 | # with: 43 | # node-version: 12 44 | # registry-url: https://npm.pkg.github.com/ 45 | # - run: npm ci 46 | # - run: npm publish 47 | # env: 48 | # NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .nyc_output 4 | .DS_Store 5 | *.log 6 | .vscode 7 | .idea 8 | dist 9 | compiled 10 | .awcache 11 | .rpt2_cache 12 | docs 13 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "**/{src,test}/**/*.{ts,js}": [ 3 | "prettier --write", 4 | "git add" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoporetech/jmespath-plus/6e99f78fe4779030fcf11807cd43abe98f50c50e/.nojekyll -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/tests/fixtures/**/* 2 | **/dist 3 | **/coverage 4 | **/.vscode 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSpacing: true, 4 | printWidth: 120, 5 | proseWrap: 'preserve', 6 | quoteProps: "as-needed", 7 | semi: true, 8 | singleQuote: true, 9 | tabWidth: 2, 10 | trailingComma: 'all', 11 | useTabs: false, 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /LODASH_SUPPORT.md: -------------------------------------------------------------------------------- 1 | ## Lodash functions supported in `@metrichor/jmespath-plus` 2 | 3 | ### ARRAY 4 | 5 | - chunk 6 | - compact 7 | - concat 8 | - difference 9 | - differenceBy 10 | - differenceWith 11 | - drop 12 | - dropRight 13 | - dropRightWhile 14 | - dropWhile 15 | - fill 16 | - findIndex 17 | - findLastIndex 18 | - first 19 | - flatten 20 | - flattenDeep 21 | - flattenDepth 22 | - fromPairs 23 | - head 24 | - indexOf 25 | - initial 26 | - intersection 27 | - join 28 | - last 29 | - lastIndexOf 30 | - nth 31 | - pull 32 | - pullAll 33 | - pullAllBy 34 | - pullAt 35 | - remove 36 | - reverse 37 | - slice 38 | - sortedIndex 39 | - sortedIndexBy 40 | - sortedIndexOf 41 | - sortedLastIndex 42 | - sortedLastIndexBy 43 | - sortedLastIndexOf 44 | - sortedUniq 45 | - tail 46 | - take 47 | - takeRight 48 | - takeRightWhile 49 | - takeWhile 50 | - union 51 | - uniq 52 | - uniqBy 53 | - unzip 54 | - without 55 | - xor 56 | - zip 57 | - zipObject 58 | - zipObjectDeep 59 | 60 | 61 | ### COLLECTION 62 | 63 | - countBy 64 | - each 65 | - eachRight 66 | - every 67 | - filter 68 | - find 69 | - findLast 70 | - flatMap 71 | - flatMapDeep 72 | - flatMapDepth 73 | - forEach 74 | - forEachRight 75 | - groupBy 76 | - includes 77 | - invokeMap 78 | - keyBy 79 | - map 80 | - orderBy 81 | - partition 82 | - reduce 83 | - reduceRight 84 | - reject 85 | - sample 86 | - sampleSize 87 | - shuffle 88 | - size 89 | - some 90 | - sortBy 91 | 92 | 93 | ### LANG 94 | 95 | - castArray 96 | - eq 97 | - gt 98 | - gte 99 | - lt 100 | - lte 101 | 102 | 103 | ### MATH 104 | 105 | - add 106 | - ceil 107 | - divide 108 | - floor 109 | - max 110 | - maxBy 111 | - mean 112 | - meanBy 113 | - min 114 | - minBy 115 | - multiply 116 | - round 117 | - subtract 118 | - sum 119 | - sumBy 120 | 121 | 122 | ### NUMBER 123 | 124 | - clamp 125 | - inRange 126 | - random 127 | 128 | 129 | ### OBJECT 130 | 131 | - assign 132 | - assignIn 133 | - at 134 | - defaults 135 | - defaultsDeep 136 | - entries 137 | - extend 138 | - findKey 139 | - findLastKey 140 | - get 141 | - has 142 | - invert 143 | - invertBy 144 | - invoke 145 | - keys 146 | - mapKeys 147 | - mapValues 148 | - merge 149 | - omit 150 | - omitBy 151 | - pick 152 | - pickBy 153 | - set 154 | - toPairs 155 | - transform 156 | - unset 157 | - update 158 | - values 159 | 160 | 161 | ### STRING 162 | 163 | - camelCase 164 | - capitalize 165 | - deburr 166 | - endsWith 167 | - escape 168 | - escapeRegExp 169 | - kebabCase 170 | - lowerCase 171 | - lowerFirst 172 | - pad 173 | - padEnd 174 | - padStart 175 | - parseInt 176 | - repeat 177 | - replace 178 | - snakeCase 179 | - split 180 | - startCase 181 | - startsWith 182 | - toLower 183 | - toUpper 184 | - trim 185 | - trimEnd 186 | - trimStart 187 | - unescape 188 | - upperCase 189 | - upperFirst 190 | - words 191 | 192 | 193 | ### UTIL 194 | 195 | - range 196 | - rangeRight 197 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Node.js CI](https://github.com/nanoporetech/jmespath-plus/workflows/Node.js%20CI/badge.svg?branch=master) 2 | 3 | # @metrichor/jmespath-plus 4 | 5 | 6 | @metrichor/jmespath-plus extends [@metrichor/jmespath](https://www.npmjs.com/package/@metrichor/jmespath) with `lodash` functions that map well to JSON objects as well as a few extra typed functions that are native to this library. 7 | 8 | JMESPath is a query language for JSON. It will take a JSON document 9 | as input and transform it into another JSON document 10 | given a JMESPath expression. 11 | 12 | Where this library departs is by adding a number of extra functions to the JMESPath expressions that are helpful if you require more powerful JSON transforms of simpler expressions. 13 | 14 | --- 15 | 16 | ## INSTALLATION 17 | 18 | ``` 19 | npm i @metrichor/jmespath-plus 20 | ``` 21 | 22 | --- 23 | 24 | ## JMESPATH BUILT-INS 25 | 26 | The current [JMESPath spec](https://jmespath.org/specification.html#built-in-functions) already describes a number of built-in functions that my be sufficient. These are already included in this library 27 | 28 | --- 29 | 30 | ## __JMESPATH-PLUS__ EXTENSIONS 31 | 32 | - `as_lambda` - Convert strings to anonymous functions to be used as lodash predicates 33 | - `as_regexp` - Convert strings to `Regexp` objects to be used as lodash arguments 34 | - `toJSON` - Convert JS objects to JSON strings 35 | - `fromJSON` - Convert JSON strings to JS objects 36 | - `mean` - Calculate the mean/average of an array of numbers 37 | - `mode` - Calculate the most common number in an array of numbers 38 | - `median` - Calculate the middle value from an array of numbers 39 | - `toFixed` - Set the precision of a float 40 | - `formatNumber` - Format a number with units at a set precision 41 | - `uniq` - De-duplicate a list of values 42 | - `mod` - Calculate the modulus of two numbers 43 | - `divide` - Divide two numbers 44 | - `split` - Split a string on a given character or character sequence 45 | - `entries` - Flatten a hash into key, value tuples 46 | - `format` - Format a string given a template and input values (array/object) 47 | - `flatMapValue` - Flatten all values in a object into key, value tuples 48 | - `toUpperCase` - Uppercase (locale based) all characters in a string 49 | - `toLowerCase` - Lowercase (locale based) all characters in a string 50 | - `trim` - Remove flanking whitespace from a string 51 | - `groupBy` - Group an array of objects by a value or expression 52 | - `combine` - Create an object from a tuple of key, value pairs (inverse of entries) 53 | 54 | --- 55 | 56 | ## __LODASH__ EXTENSIONS 57 | 58 | Most Lodash functions that apply to JSON types are included as JMESPath function expressions and are __prefixed with an `_` character__ to ensure no name clashes and overwrites with the built-in functions. 59 | 60 | For example the [lodash zip] function: 61 | 62 | ```tsx 63 | /* In Javascript this looks as follows... */ 64 | 65 | _.zip(['a', 'b'], [1, 2], [true, false]); 66 | // => [['a', 1, true], ['b', 2, false]] 67 | 68 | 69 | /* In JMESPath however, this looks as follows... */ 70 | 71 | search([['a', 'b'], [1, 2], [true, false]], '_zip([0], [1], [2])') 72 | 73 | // => [['a', 1, true], ['b', 2, false]] 74 | 75 | ``` 76 | 77 | **`NOT ALL LODASH FUNCTIONS HAVE BEEN INCLUDED!!!!`** 78 | 79 | Many lodash functions don't necessarily map to JSON objects. For a complete list of the __`165 lodash`__ functions that are included in jmespath-plus go [HERE](./LODASH_SUPPORT.md) 80 | 81 | ### DEALING WITH PREDICATES 82 | 83 | Many lodash functions accept function predicates as arguments. This is still possible in jmespath-plus by using a new built-in function that converts strings to functions (`as_lambda`). For example: 84 | 85 | ```javascript 86 | 87 | // `_findKey` JMESPath function extension 88 | 89 | const { search } = require('@metrichor/jmespath-plus'); 90 | 91 | const users = { 92 | barney: { age: 36, active: true }, 93 | fred: { age: 40, active: false }, 94 | pebbles: { age: 1, active: true }, 95 | }; 96 | 97 | assert(search(users, "_findKey(@, as_lambda('o => o.age < 40'))") === 'barney'); 98 | 99 | ``` 100 | 101 | --- 102 | 103 | ## USAGE 104 | 105 | ### `search(data: JSONValue, expression: string): JSONValue` 106 | 107 | ```javascript 108 | /* using ES modules */ 109 | import { search } from '@metrichor/jmespath-plus'; 110 | 111 | 112 | /* using CommonJS modules */ 113 | const search = require('@metrichor/jmespath-plus').search; 114 | 115 | 116 | search({foo: {bar: {baz: [0, 1, 2, 3, 4]}}}, "foo.bar.baz[2]") 117 | 118 | // OUTPUTS: 2 119 | 120 | ``` 121 | 122 | In the example we gave the `search` function input data of 123 | `{foo: {bar: {baz: [0, 1, 2, 3, 4]}}}` as well as the JMESPath 124 | expression `foo.bar.baz[2]`, and the `search` function evaluated 125 | the expression against the input data to produce the result `2`. 126 | 127 | The JMESPath language can do *a lot* more than select an element 128 | from a list. Here are a few more examples: 129 | 130 | ```javascript 131 | import { search } from '@metrichor/jmespath-plus'; 132 | 133 | /* --- EXAMPLE 1 --- */ 134 | 135 | let JSON_DOCUMENT = { 136 | foo: { 137 | bar: { 138 | baz: [0, 1, 2, 3, 4] 139 | } 140 | } 141 | }; 142 | 143 | search(JSON_DOCUMENT, "foo.bar"); 144 | // OUTPUTS: { baz: [ 0, 1, 2, 3, 4 ] } 145 | 146 | 147 | /* --- EXAMPLE 2 --- */ 148 | 149 | JSON_DOCUMENT = { 150 | "foo": [ 151 | {"first": "a", "last": "b"}, 152 | {"first": "c", "last": "d"} 153 | ] 154 | }; 155 | 156 | search(JSON_DOCUMENT, "foo[*].first") 157 | // OUTPUTS: [ 'a', 'c' ] 158 | 159 | 160 | /* --- EXAMPLE 3 --- */ 161 | 162 | JSON_DOCUMENT = { 163 | "foo": [ 164 | {"age": 20}, 165 | {"age": 25}, 166 | {"age": 30}, 167 | {"age": 35}, 168 | {"age": 40} 169 | ] 170 | } 171 | 172 | search(JSON_DOCUMENT, "foo[?age > `30`]"); 173 | // OUTPUTS: [ { age: 35 }, { age: 40 } ] 174 | ``` 175 | 176 | 177 | ### `registerFunction(functionName: string, customFunction: RuntimeFunction, signature: InputSignature[]): void` 178 | 179 | Extend the list of built in JMESpath expressions with your own functions. 180 | 181 | ```javascript 182 | import {search, registerFunction, TYPE_NUMBER} from '@metrichor/jmespath-plus' 183 | 184 | 185 | search({ foo: 60, bar: 10 }, 'divide(foo, bar)') 186 | // THROWS ERROR: Error: Unknown function: divide() 187 | 188 | registerFunction( 189 | 'divide', // FUNCTION NAME 190 | (resolvedArgs) => { // CUSTOM FUNCTION 191 | const [dividend, divisor] = resolvedArgs; 192 | return dividend / divisor; 193 | }, 194 | [{ types: [TYPE_NUMBER] }, { types: [TYPE_NUMBER] }] //SIGNATURE 195 | ); 196 | 197 | search({ foo: 60,bar: 10 }, 'divide(foo, bar)'); 198 | // OUTPUTS: 6 199 | 200 | ``` 201 | 202 | ### `compile(expression: string): ExpressionNodeTree` 203 | 204 | You can precompile all your expressions ready for use later on. the `compile` 205 | function takes a JMESPath expression and returns an abstract syntax tree that 206 | can be used by the TreeInterpreter function 207 | 208 | ```javascript 209 | import { compile, TreeInterpreter } from '@metrichor/jmespath'; 210 | 211 | const ast = compile('foo.bar'); 212 | 213 | TreeInterpreter.search(ast, {foo: {bar: 'BAZ'}}) 214 | // RETURNS: "BAZ" 215 | 216 | ``` 217 | 218 | ## More Resources 219 | 220 | The example above only show a small amount of what 221 | a JMESPath expression can do. If you want to take a 222 | tour of the language, the *best* place to go is the 223 | [JMESPath Tutorial](http://jmespath.org/tutorial.html). 224 | 225 | One of the best things about JMESPath is that it is 226 | implemented in many different programming languages including 227 | python, ruby, php, lua, etc. To see a complete list of libraries, 228 | check out the [JMESPath libraries page](http://jmespath.org/libraries.html). 229 | 230 | And finally, the full JMESPath specification can be found 231 | on the [JMESPath site](http://jmespath.org/specification.html). 232 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // jest.config.js 2 | module.exports = { 3 | transform: { 4 | ".(ts|js|tsx)": "ts-jest" 5 | }, 6 | testEnvironment: "node", 7 | testPathIgnorePatterns: ['.vscode', 'dist', 'node_modules'], 8 | maxConcurrency: 1, 9 | testRegex: '(/test/.*|\\.?(test|spec))\\.(tsx?|jsx?)$', 10 | moduleFileExtensions: [ 11 | "ts", 12 | "tsx", 13 | "js" 14 | ], 15 | coveragePathIgnorePatterns: [ 16 | "/node_modules/", 17 | "/dist/", 18 | "/test/" 19 | ], 20 | coverageThreshold: { 21 | global: { 22 | branches: 90, 23 | functions: 95, 24 | lines: 95, 25 | statements: 95 26 | } 27 | }, 28 | collectCoverageFrom: [ 29 | "src/*.{js,ts}" 30 | ] 31 | }; 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metrichor/jmespath-plus", 3 | "description": "Level-up your JMESPath expressions with lodash functions!", 4 | "version": "0.5.3", 5 | "author": { 6 | "name": "Oxford Nanopore Technologies", 7 | "email": "support@nanoporetech.com", 8 | "url": "https://nanoporetech.com" 9 | }, 10 | "main": "dist/jmespath-plus.umd.js", 11 | "module": "dist/jmespath-plus.esm.js", 12 | "typings": "dist/types/index.d.ts", 13 | "types": "dist/types/index.d.ts", 14 | "files": [ 15 | "dist" 16 | ], 17 | "homepage": "https://github.com/nanoporetech/jmespath-plus#readme", 18 | "bugs": { 19 | "url": "https://github.com/nanoporetech/jmespath-plus/issues", 20 | "mail": "" 21 | }, 22 | "keywords": [ 23 | "jmespath", 24 | "jmespath-plus", 25 | "jsonpath", 26 | "query", 27 | "json", 28 | "jq", 29 | "xpath" 30 | ], 31 | "contributors": [ 32 | { 33 | "name": "Glen van Ginkel", 34 | "url": "https://github.com/glenveegee" 35 | }, 36 | { 37 | "name": "Iain Shorter", 38 | "url": "https://github.com/shortercode" 39 | }, 40 | { 41 | "name": "Darren Mothersele", 42 | "url": "https://github.com/darrenmothersele" 43 | } 44 | ], 45 | "repository": { 46 | "type": "git", 47 | "url": "git://github.com/nanoporetech/jmespath-plus" 48 | }, 49 | "license": "MPL-2.0", 50 | "engines": { 51 | "node": ">=10.0.0" 52 | }, 53 | "scripts": { 54 | "lint": "eslint --ignore-path .eslintignore './{src,test}/**/*.{js,ts}'", 55 | "lint:fix": "npm run lint -- --fix", 56 | "prebuild": "rimraf dist", 57 | "perf": "node scripts/perf.js", 58 | "build": "tsc --outDir dist/lib -d --module commonjs && rollup -c rollup.config.ts", 59 | "start": "rollup -c rollup.config.ts -w", 60 | "test": "jest --coverage", 61 | "test:watch": "npm run test -- --watch", 62 | "test:prod": "npm run lint && npm run test -- --no-cache", 63 | "deploy-docs": "ts-node scripts/gh-pages-publish", 64 | "report-coverage": "cat ./coverage/lcov.info | coveralls", 65 | "precommit": "lint-staged" 66 | }, 67 | "peerDependencies": { 68 | "lodash": "^4.17.21", 69 | "typescript": "^4.2.4" 70 | }, 71 | "devDependencies": { 72 | "@rollup/plugin-commonjs": "^19.0.0", 73 | "@rollup/plugin-json": "^4.1.0", 74 | "@rollup/plugin-node-resolve": "^13.0.0", 75 | "@rollup/plugin-typescript": "^8.2.1", 76 | "@types/jest": "^26.0.23", 77 | "@types/lodash": "^4.14.170", 78 | "@typescript-eslint/eslint-plugin": "^4.26.0", 79 | "@typescript-eslint/parser": "^4.26.0", 80 | "benchmark": "^2.1.4", 81 | "coveralls": "^3.1.0", 82 | "eslint": "^7.27.0", 83 | "eslint-config-prettier": "^8.3.0", 84 | "eslint-plugin-prettier": "^3.4.0", 85 | "husky": "^6.0.0", 86 | "jest": "^26.6.3", 87 | "jest-cli": "^26.6.3", 88 | "jest-config": "^26.6.3", 89 | "lint-staged": "^11.0.0", 90 | "prettier": "^2.3.0", 91 | "prettier-eslint": "^12.0.0", 92 | "rimraf": "^3.0.2", 93 | "rollup": "^2.50.5", 94 | "rollup-plugin-sourcemaps": "^0.6.3", 95 | "rollup-plugin-terser": "^7.0.2", 96 | "shelljs": "^0.8.4", 97 | "ts-jest": "^26.5.6" 98 | }, 99 | "dependencies": { 100 | "@metrichor/jmespath": "^0.3.1" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import sourceMaps from 'rollup-plugin-sourcemaps'; 4 | import typescript from '@rollup/plugin-typescript'; 5 | import json from '@rollup/plugin-json'; 6 | import { terser } from 'rollup-plugin-terser'; 7 | 8 | const pkg = require('./package.json'); 9 | 10 | const libraryName = 'jmespath-plus'; 11 | 12 | export default { 13 | input: `src/index.ts`, 14 | output: [ 15 | { file: pkg.main, name: libraryName, format: 'umd', exports: 'named', sourcemap: false }, 16 | { 17 | file: pkg.main.replace('umd.js', 'umd.min.js'), 18 | name: libraryName, 19 | format: 'umd', 20 | exports: 'named', 21 | sourcemap: true, 22 | plugins: [terser()], 23 | }, 24 | { file: pkg.module, format: 'esm', exports: 'named', sourcemap: false }, 25 | { 26 | file: pkg.module.replace('esm.js', 'esm.min.js'), 27 | format: 'esm', 28 | exports: 'named', 29 | sourcemap: true, 30 | plugins: [terser()], 31 | }, 32 | ], 33 | // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash') 34 | external: ['lodash'], 35 | watch: { 36 | include: ['src/**'], 37 | }, 38 | plugins: [ 39 | // Allow json resolution 40 | json(), 41 | // Compile TypeScript files 42 | typescript(), 43 | // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs) 44 | commonjs({ extensions: ['.js', '.ts'] }), 45 | // Allow node_modules resolution, so you can use 'external' to control 46 | // which external modules to include in the bundle 47 | // https://github.com/rollup/rollup-plugin-node-resolve#usage 48 | resolve(), 49 | 50 | // Resolve source maps to the original source 51 | sourceMaps(), 52 | ], 53 | }; 54 | -------------------------------------------------------------------------------- /scripts/gh-pages-publish.js: -------------------------------------------------------------------------------- 1 | const { cd, exec, echo, touch } = require('shelljs'); 2 | const { readFileSync } = require('fs'); 3 | const url = require('url'); 4 | 5 | let repoUrl; 6 | const pkg = JSON.parse(readFileSync('package.json')); 7 | if (typeof pkg.repository === 'object') { 8 | if (!pkg.repository.hasOwnProperty('url')) { 9 | throw new Error('URL does not exist in repository section'); 10 | } 11 | repoUrl = pkg.repository.url; 12 | } else { 13 | repoUrl = pkg.repository; 14 | } 15 | 16 | const parsedUrl = url.parse(repoUrl); 17 | const repository = (parsedUrl.host || '') + (parsedUrl.path || ''); 18 | const ghToken = process.env.GH_TOKEN; 19 | 20 | echo('Deploying docs!!!'); 21 | cd('docs'); 22 | touch('.nojekyll'); 23 | exec('git init'); 24 | exec('git add .'); 25 | exec('git config user.name "Metrichor"'); 26 | exec('git config user.email "support@nanoporetech.com"'); 27 | exec('git commit -m "docs(docs): update gh-pages"'); 28 | exec(`git push --force --quiet "https://${ghToken}@${repository}" master:gh-pages`); 29 | echo('Docs deployed!!'); 30 | -------------------------------------------------------------------------------- /scripts/jp.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | const jmespath = require('../dist/lib'); 3 | 4 | process.stdin.setEncoding('utf-8'); 5 | 6 | 7 | if (process.argv.length < 2) { 8 | console.log("Must provide a jmespath expression."); 9 | process.exit(1); 10 | } 11 | var inputJSON = ""; 12 | 13 | process.stdin.on('readable', function() { 14 | var chunk = process.stdin.read(); 15 | if (chunk !== null) { 16 | inputJSON += chunk; 17 | } 18 | }); 19 | 20 | process.stdin.on('end', function() { 21 | var parsedInput = JSON.parse(inputJSON); 22 | console.log(JSON.stringify(jmespath.search(parsedInput, process.argv[2]))); 23 | }); 24 | -------------------------------------------------------------------------------- /scripts/perf.js: -------------------------------------------------------------------------------- 1 | const jmespath = require('../dist/lib') 2 | const Benchmark = require('benchmark'); 3 | const suite = new Benchmark.Suite; 4 | 5 | // add tests 6 | suite.add('Parser#single_expr', function() { 7 | jmespath.compile("foo"); 8 | }) 9 | .add('Parser#single_subexpr', function() { 10 | jmespath.compile("foo.bar"); 11 | }) 12 | .add('Parser#deeply_nested_50', function() { 13 | jmespath.compile("j49.j48.j47.j46.j45.j44.j43.j42.j41.j40.j39.j38.j37.j36.j35.j34.j33.j32.j31.j30.j29.j28.j27.j26.j25.j24.j23.j22.j21.j20.j19.j18.j17.j16.j15.j14.j13.j12.j11.j10.j9.j8.j7.j6.j5.j4.j3.j2.j1.j0"); 14 | 15 | }) 16 | .add('Parser#deeply_nested_50_index', function() { 17 | jmespath.compile("[49][48][47][46][45][44][43][42][41][40][39][38][37][36][35][34][33][32][31][30][29][28][27][26][25][24][23][22][21][20][19][18][17][16][15][14][13][12][11][10][9][8][7][6][5][4][3][2][1][0]"); 18 | }) 19 | .add('Parser#basic_list_projection', function() { 20 | jmespath.compile("foo[*].bar"); 21 | }) 22 | .on('cycle', function(event) { 23 | const bench = event.target; 24 | const mean = bench.stats.mean * 1000; 25 | // const variance = bench.stats.variance * 1000000; 26 | let result = 'Mean time: ' + mean.toFixed(6) + 'msec '; 27 | result += event.target.toString(); 28 | console.log(result); 29 | }) 30 | .on('complete', function() { 31 | }) 32 | // run async 33 | .run({ 'async': false }); 34 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | FunctionSignature, 3 | InputSignature, 4 | RuntimeFunction, 5 | ObjectDict, 6 | JSONArray, 7 | JSONObject, 8 | JSONPrimitive, 9 | JSONValue, 10 | } from '@metrichor/jmespath/dist/types'; 11 | export type { 12 | NumberScaleOptions, 13 | ScaleDefinitions, 14 | Scale, 15 | ScaleUnit, 16 | ScalePrefixDefinition, 17 | } from './utils/number-scale'; 18 | export { numberScale } from './utils/number-scale'; 19 | import { jmespath as JMESPath } from '@metrichor/jmespath'; 20 | 21 | import { loadPlugins } from './plugins'; 22 | 23 | export const TYPE_ANY = JMESPath.TYPE_ANY; 24 | export const TYPE_ARRAY = JMESPath.TYPE_ARRAY; 25 | export const TYPE_ARRAY_NUMBER = JMESPath.TYPE_ARRAY_NUMBER; 26 | export const TYPE_ARRAY_STRING = JMESPath.TYPE_ARRAY_STRING; 27 | export const TYPE_BOOLEAN = JMESPath.TYPE_BOOLEAN; 28 | export const TYPE_EXPREF = JMESPath.TYPE_EXPREF; 29 | export const TYPE_NULL = JMESPath.TYPE_NULL; 30 | export const TYPE_NUMBER = JMESPath.TYPE_NUMBER; 31 | export const TYPE_OBJECT = JMESPath.TYPE_OBJECT; 32 | export const TYPE_STRING = JMESPath.TYPE_STRING; 33 | 34 | export const compile = JMESPath.compile; 35 | export const tokenize = JMESPath.tokenize; 36 | export const registerFunction = JMESPath.registerFunction; 37 | export const search = JMESPath.search; 38 | export const TreeInterpreter = JMESPath.TreeInterpreter; 39 | 40 | export const jmespath = loadPlugins() && JMESPath; 41 | 42 | export default { 43 | compile, 44 | jmespath, 45 | registerFunction, 46 | search, 47 | tokenize, 48 | TreeInterpreter, 49 | TYPE_ANY, 50 | TYPE_ARRAY_NUMBER, 51 | TYPE_ARRAY_STRING, 52 | TYPE_ARRAY, 53 | TYPE_BOOLEAN, 54 | TYPE_EXPREF, 55 | TYPE_NULL, 56 | TYPE_NUMBER, 57 | TYPE_OBJECT, 58 | TYPE_STRING, 59 | }; 60 | -------------------------------------------------------------------------------- /src/plugins.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-call */ 2 | import { 3 | registerFunction, 4 | TYPE_ANY, 5 | TYPE_ARRAY, 6 | TYPE_ARRAY_NUMBER, 7 | TYPE_EXPREF, 8 | TYPE_NUMBER, 9 | TYPE_OBJECT, 10 | TYPE_STRING, 11 | JSONObject, 12 | JSONArray, 13 | TYPE_BOOLEAN, 14 | TYPE_NULL, 15 | TYPE_ARRAY_STRING, 16 | } from '@metrichor/jmespath'; 17 | import * as _lodash from 'lodash'; 18 | import { numberScale } from './utils/number-scale'; 19 | import { SUPPORTED_FUNCTIONS } from './supportedFunctions'; 20 | import { ExpressionNodeTree } from '@metrichor/jmespath/dist/types/Lexer'; 21 | 22 | type UnknownFunction = (this: ThisType, ...args: unknown[]) => unknown; 23 | 24 | export const loadPlugins = (): boolean => { 25 | registerFunction( 26 | 'as_regexp', 27 | ([pattern, flags = '']: [string, string]): RegExp => { 28 | return new RegExp(pattern, flags); 29 | }, 30 | [{ types: [TYPE_STRING] }, { types: [TYPE_STRING], optional: true }], 31 | ); 32 | 33 | registerFunction( 34 | 'as_lambda', 35 | ([fnString]: [string]): UnknownFunction => { 36 | // eslint-disable-next-line @typescript-eslint/no-implied-eval 37 | const lambdaConstructor = new Function(`return ${fnString}`); 38 | const lambda = lambdaConstructor.call({}).bind({}); 39 | return lambda; 40 | }, 41 | [{ types: [TYPE_STRING] }], 42 | ); 43 | 44 | registerFunction( 45 | 'mean', 46 | ([vector]: [number[]]) => { 47 | return vector.reduce((a, b) => a + b, 0) / vector.length; 48 | }, 49 | [{ types: [TYPE_ARRAY_NUMBER] }], 50 | ); 51 | 52 | registerFunction( 53 | 'mode', 54 | ([vector]: [number[]]) => { 55 | if (!vector.length) { 56 | return null; 57 | } 58 | const modeTracker = vector 59 | .sort((a: number, b: number) => a - b) 60 | .reduce((valueCount: { [mode: number]: [number, number] }, newValue: any) => { 61 | const valueKey = Number.parseFloat(newValue); 62 | valueCount[valueKey] = valueCount[valueKey] 63 | ? [valueCount[valueKey][0] + 1, valueCount[valueKey][1]] 64 | : [1, newValue]; 65 | return valueCount; 66 | }, {}); 67 | const maxOccurrence = Math.max(...Object.values(modeTracker).map(x => x[0])); 68 | if (maxOccurrence === 1 && vector.length > 1) { 69 | return null; 70 | } 71 | return Object.values(modeTracker) 72 | .filter(([occurrence]) => occurrence === maxOccurrence) 73 | .map(v => v[1]); 74 | }, 75 | [{ types: [TYPE_ARRAY_NUMBER] }], 76 | ); 77 | 78 | registerFunction( 79 | 'median', 80 | ([vector]: [number[]]) => { 81 | if (!vector.length) { 82 | return null; 83 | } 84 | const sorted = vector.sort((a: number, b: number) => a - b); 85 | const halfway = vector.length / 2; 86 | if (vector.length % 2 === 0) { 87 | return (sorted[halfway - 1] + sorted[halfway]) / 2; 88 | } 89 | return sorted[Math.floor(halfway)]; 90 | }, 91 | [{ types: [TYPE_ARRAY_NUMBER] }], 92 | ); 93 | 94 | registerFunction( 95 | 'toFixed', 96 | ([toFormat, precision]) => { 97 | return `${Number.parseFloat(toFormat || 0.0).toFixed(precision)}`; 98 | }, 99 | [{ types: [TYPE_NUMBER] }, { types: [TYPE_NUMBER] }], 100 | ); 101 | 102 | registerFunction( 103 | 'formatNumber', 104 | ([toFormat, precision, unit]: [number, number, string]) => { 105 | const formattedNumber = numberScale(toFormat || 0.0, { 106 | precision, 107 | recursive: 0, 108 | scale: 'SI', 109 | unit: unit || '', 110 | }) as string; 111 | const hasMatch = /[\d\.]+/g.exec(formattedNumber); 112 | const hasOne = hasMatch && hasMatch[0] === '1'; 113 | return `${formattedNumber}${unit && (hasOne ? '' : 's')}`; 114 | }, 115 | [{ types: [TYPE_NUMBER] }, { types: [TYPE_NUMBER] }, { types: [TYPE_STRING] }], 116 | ); 117 | 118 | registerFunction( 119 | 'uniq', 120 | ([resolvedArgs]) => { 121 | return Array.from(new Set(resolvedArgs)); 122 | }, 123 | [{ types: [TYPE_ARRAY] }], 124 | ); 125 | 126 | registerFunction( 127 | 'mod', 128 | resolvedArgs => { 129 | const [dividend, divisor] = resolvedArgs; 130 | return dividend % divisor; 131 | }, 132 | [{ types: [TYPE_NUMBER] }, { types: [TYPE_NUMBER] }], 133 | ); 134 | 135 | registerFunction( 136 | 'divide', 137 | resolvedArgs => { 138 | const [dividend, divisor] = resolvedArgs; 139 | return dividend / divisor; 140 | }, 141 | [{ types: [TYPE_NUMBER] }, { types: [TYPE_NUMBER] }], 142 | ); 143 | 144 | registerFunction( 145 | 'split', 146 | ([splitChar, toSplit]: [string, string]) => { 147 | return toSplit.split(splitChar); 148 | }, 149 | [{ types: [TYPE_STRING] }, { types: [TYPE_STRING] }], 150 | ); 151 | 152 | registerFunction( 153 | 'entries', 154 | ([resolvedArgs]) => { 155 | return Object.entries(resolvedArgs); 156 | }, 157 | [{ types: [TYPE_OBJECT, TYPE_ARRAY] }], 158 | ); 159 | 160 | registerFunction( 161 | 'format', 162 | ([template, templateStringsMap]: [string, JSONObject | JSONArray]) => { 163 | let newTemplate = template; 164 | for (const attr in templateStringsMap) { 165 | const rgx = new RegExp(`\\$\\{${attr}\\}`, 'g'); 166 | newTemplate = newTemplate.replace(rgx, templateStringsMap[attr] ?? ''); 167 | } 168 | return newTemplate; 169 | }, 170 | [{ types: [TYPE_STRING] }, { types: [TYPE_OBJECT, TYPE_ARRAY] }], 171 | ); 172 | 173 | registerFunction( 174 | 'flatMapValues', 175 | ([inputObject]) => { 176 | return Object.entries(inputObject).reduce((flattened, entry) => { 177 | const [key, value]: [string, any] = entry; 178 | 179 | if (Array.isArray(value)) { 180 | return [...flattened, ...value.map(v => [key, v])]; 181 | } 182 | return [...flattened, [key, value]]; 183 | }, [] as any[]); 184 | }, 185 | [{ types: [TYPE_OBJECT, TYPE_ARRAY] }], 186 | ); 187 | 188 | registerFunction( 189 | 'toUpperCase', 190 | ([inputString]: [string]) => { 191 | return inputString.toLocaleUpperCase(); 192 | }, 193 | [{ types: [TYPE_STRING] }], 194 | ); 195 | 196 | registerFunction( 197 | 'toLowerCase', 198 | ([inputString]: [string]) => { 199 | return inputString.toLocaleLowerCase(); 200 | }, 201 | [{ types: [TYPE_STRING] }], 202 | ); 203 | 204 | registerFunction( 205 | 'trim', 206 | ([inputString]: [string]) => { 207 | return inputString.trim(); 208 | }, 209 | [{ types: [TYPE_STRING] }], 210 | ); 211 | 212 | registerFunction( 213 | 'groupBy', 214 | function (this: any, [memberList, exprefNode]: [JSONObject[], ExpressionNodeTree | string]) { 215 | if (!this._interpreter) { 216 | return {}; 217 | } 218 | 219 | if (typeof exprefNode === 'string') { 220 | return memberList.reduce((grouped, member) => { 221 | if (exprefNode in member) { 222 | const key = member[exprefNode] as string; 223 | const currentMembers = (grouped[key] as any[]) || []; 224 | grouped[key] = [...currentMembers, member]; 225 | } 226 | return grouped; 227 | }, {}); 228 | } 229 | const interpreter = this._interpreter; 230 | const requiredType = Array.from(new Set(memberList.map(member => this.getTypeName(member)))); 231 | const onlyObjects = requiredType.every(x => x === TYPE_OBJECT); 232 | if (!onlyObjects) { 233 | throw new Error( 234 | `TypeError: unexpected type. Expected Array but received Array<${requiredType 235 | .map(type => this.TYPE_NAME_TABLE[type]) 236 | .join(' | ')}>`, 237 | ); 238 | } 239 | 240 | return memberList.reduce((grouped, member) => { 241 | const key = interpreter.visit(exprefNode, member) as string; 242 | const currentMembers = (grouped[key] as any[]) || []; 243 | grouped[key] = [...currentMembers, member]; 244 | return grouped; 245 | }, {}); 246 | }, 247 | [{ types: [TYPE_ARRAY] }, { types: [TYPE_EXPREF, TYPE_STRING] }], 248 | ); 249 | 250 | registerFunction( 251 | 'combine', 252 | ([resolvedArgs]: [JSONObject[]]) => { 253 | let merged = {}; 254 | for (let i = 0; i < resolvedArgs.length; i += 1) { 255 | const current = resolvedArgs[i]; 256 | merged = Array.isArray(current) ? Object.assign(merged, ...current) : Object.assign(merged, current); 257 | } 258 | return merged; 259 | }, 260 | [ 261 | { 262 | types: [TYPE_OBJECT, TYPE_ARRAY], 263 | variadic: true, 264 | }, 265 | ], 266 | ); 267 | 268 | registerFunction( 269 | 'toJSON', 270 | ([value, replacer, space]: [JSONObject, null | string[], number | string]) => { 271 | return JSON.stringify(value, replacer ?? null, space ?? 0); 272 | }, 273 | [ 274 | { types: [TYPE_OBJECT, TYPE_ARRAY, TYPE_STRING, TYPE_BOOLEAN, TYPE_NULL, TYPE_NUMBER] }, 275 | { types: [TYPE_NULL, TYPE_ARRAY_STRING], optional: true }, 276 | { types: [TYPE_NUMBER, TYPE_STRING], optional: true }, 277 | ], 278 | ); 279 | 280 | registerFunction( 281 | 'fromJSON', 282 | ([value]: [string]) => { 283 | return JSON.parse(value); 284 | }, 285 | [{ types: [TYPE_STRING] }], 286 | ); 287 | 288 | for (const [key, signature] of SUPPORTED_FUNCTIONS) { 289 | registerFunction( 290 | `_${key}`, 291 | (resolvedArgs: any) => { 292 | return resolvedArgs.length > 1 ? _lodash[key](...resolvedArgs) : _lodash[key](resolvedArgs[0]); 293 | }, 294 | signature ?? [ 295 | { 296 | types: [TYPE_ANY], 297 | variadic: true, 298 | }, 299 | ], 300 | ); 301 | } 302 | 303 | return true; 304 | }; 305 | -------------------------------------------------------------------------------- /src/utils/number-scale.ts: -------------------------------------------------------------------------------- 1 | export interface NumberScaleOptions { 2 | precision: number; 3 | roundMode?: 'even' | 'odd' | 'up' | 'down'; 4 | scale: string; 5 | unit: string; 6 | recursive: number; 7 | } 8 | 9 | export interface ScaleDefinitions { 10 | [scaleKey: string]: Scale; 11 | } 12 | 13 | export interface Scale { 14 | list: ScaleUnit[]; 15 | map: { 16 | [unit: string]: number; 17 | }; 18 | re: RegExp; 19 | base: number | string; 20 | } 21 | 22 | export type ScaleUnit = [string, number]; 23 | 24 | export interface ScalePrefixDefinition { 25 | [unit: string]: number; 26 | } 27 | 28 | /** 29 | * The default options 30 | * 31 | * @namespace defaultOptions 32 | * @property {Number} precision the decimal precision to display (0 for integers only) 33 | * @property {string} roundMode one of `even`, `odd`, `up`, `down`, or any other value 34 | * @property {string} scale the scale key to use 35 | * @property {string} unit the unit suffix to display 36 | * @property {Number} recursive a positive integer to recursively decompose the value into scale fragments 37 | * @see formatPrecision 38 | * @see round 39 | */ 40 | const defaultOptions: NumberScaleOptions = { 41 | //maxExponent: 308, 42 | //minExponent: -324, 43 | precision: 2, 44 | roundMode: 'up', 45 | scale: 'SI', 46 | unit: '', 47 | recursive: 0, 48 | }; 49 | /* @private */ 50 | const defaultOptionsKeys = Object.keys(defaultOptions); 51 | /** 52 | * All known scales 53 | * 54 | * @namespace 55 | * @private 56 | * @property {Object} SI International System of Units (https://en.wikipedia.org/wiki/International_System_of_Units) 57 | * @property {Object} time Time Unit scale 58 | * @property {object} IEEE1541 IEEE 1541 Units for bytes measurements 59 | */ 60 | const knownScales: ScaleDefinitions = { 61 | SI: buildScale( 62 | { 63 | y: 1e-24, 64 | z: 1e-21, 65 | a: 1e-18, 66 | f: 1e-15, 67 | p: 1e-12, 68 | n: 1e-9, 69 | µ: 1e-6, 70 | m: 1e-3, 71 | '': 1, 72 | k: 1e3, 73 | M: 1e6, 74 | G: 1e9, 75 | T: 1e12, 76 | P: 1e15, 77 | E: 1e18, 78 | Z: 1e21, 79 | Y: 1e24, 80 | }, 81 | 1, 82 | ), 83 | time: buildScale( 84 | { 85 | ns: 1e-9, 86 | ms: 1e-3, 87 | s: 1, 88 | m: 60, 89 | h: 3600, // 60*60 90 | d: 86400, // 60*60*24 91 | }, 92 | 1, 93 | ), 94 | IEEE1541: buildScale( 95 | { 96 | '': 1, 97 | Ki: 1e3, 98 | Mi: 1e6, 99 | Gi: 1e9, 100 | Ti: 1e12, 101 | Pi: 1e15, 102 | Ei: 1e18, 103 | Zi: 1e21, 104 | Yi: 1e24, 105 | }, 106 | 0, 107 | ), 108 | }; 109 | 110 | /** 111 | * Return an object with all the required options filled out. 112 | * Any omitted values will be fetched from the default options 113 | * 114 | * @see defaultOptions 115 | * @param {Object} options 116 | * @returns {Object} 117 | */ 118 | function mergeDefaults(options: NumberScaleOptions) { 119 | let i; 120 | let iLen; 121 | 122 | options = options || {}; 123 | 124 | for (i = 0, iLen = defaultOptionsKeys.length; i < iLen; ++i) { 125 | if (!(defaultOptionsKeys[i] in options)) { 126 | options[defaultOptionsKeys[i]] = defaultOptions[defaultOptionsKeys[i]]; 127 | } 128 | } 129 | 130 | return options; 131 | } 132 | 133 | /** 134 | * Make sure str does not contain any regex expressions 135 | * 136 | * @function escapeRegexp 137 | * @private 138 | * @param {string} str 139 | * @returns {string} 140 | */ 141 | function escapeRegexp(str: string) { 142 | return str.replace(/([.*+?=^!:${}()|[\]\/\\])/g, '\\$1'); 143 | } 144 | 145 | /** 146 | * Round the given value, considering it as a negative, or positive one. 147 | * 148 | * The function will round to the next 'even', or 'odd' number, or will round 149 | * 'down', or 'up' to any next integer. For any other value, a default mathematical 150 | * rounding will be performed (i.e. round to the nearest integer, half up) 151 | * 152 | * @function round 153 | * @private 154 | * @param {string} mode one of 'even', 'odd', 'up', 'down', or any other value 155 | * @param {boolean} neg if the value should considered as negative 156 | * @param {Number} value a value to round (i.e. with decimals) 157 | * @returns {Number} the value rounded 158 | */ 159 | function round(mode: string, neg: boolean, value: number): number { 160 | let i = value | 0; 161 | 162 | if (neg) { 163 | i = -i; 164 | value = -value; 165 | } 166 | 167 | //console.log("??? ROUND", mode, neg, value, i, i & 1); 168 | 169 | if (mode === 'even') { 170 | if ((!neg && i > value) || i & 1) { 171 | // odd 172 | value = i + 1; 173 | } else { 174 | value = i; 175 | } 176 | } else if (mode === 'odd') { 177 | if ((!neg && i > value) || i & 1) { 178 | // odd 179 | value = i; 180 | } else { 181 | value = i + 1; 182 | } 183 | } else if (mode === 'up') { 184 | value = Math.ceil(value); 185 | } else if (mode === 'down') { 186 | value = Math.floor(value); 187 | } else { 188 | value = Math.round(value); 189 | } 190 | 191 | return neg ? Math.abs(value) : value; 192 | } 193 | 194 | /** 195 | * Make sure a decimal value contains the specified precision. 196 | * Ignore any integer value. 197 | * 198 | * @function formatPrecision 199 | * @private 200 | * @param {Number} value 201 | * @param {Number} precision 202 | * @returns {string} 203 | */ 204 | function formatPrecision(value: number, precision: number): string { 205 | const i = value | 0; 206 | 207 | if (i !== value && Math.max(precision, 0)) { 208 | return Number(value).toFixed(precision); 209 | } else { 210 | return String(value); 211 | } 212 | } 213 | 214 | /** 215 | * Build a scale and prepare it to be used by the formatter and parser 216 | * 217 | * @function buildScale 218 | * @private 219 | * @param {Object} prefixes a key-value object which keys are scale units and values it's base value 220 | * @param {Number} baseUnitValue the base value, or scale pivot point 221 | * @return {Object} 222 | */ 223 | function buildScale(prefixes: ScalePrefixDefinition, baseUnitValue: number): Scale { 224 | const list: ScaleUnit[] = []; // Lists prefixes and their factor in ascending order. 225 | const map = {}; // Maps from prefixes to their factor. 226 | let unitBase: number | string = ''; // the base unit for this scale 227 | let unitTmpValue = Number.MAX_VALUE; 228 | const tmp: string[] = []; 229 | 230 | baseUnitValue = baseUnitValue || 0; 231 | 232 | Object.keys(prefixes).forEach(function (prefix) { 233 | const name = prefix; 234 | const value = prefixes[prefix]; 235 | 236 | list.push([name, value]); 237 | 238 | map[name] = value; 239 | 240 | tmp.push(escapeRegexp(name)); 241 | 242 | if (Math.abs(baseUnitValue - value) < Math.abs(baseUnitValue - unitTmpValue)) { 243 | unitBase = name; 244 | unitTmpValue = value; 245 | } 246 | }); 247 | 248 | list.sort(function (a, b) { 249 | return a[1] - b[1]; 250 | }); 251 | 252 | const sortedTmp: string = tmp 253 | .sort(function (a, b) { 254 | return b.length - a.length; // Matches longest first. 255 | }) 256 | .join('|'); 257 | const re = new RegExp('^\\s*((?:-)?\\d+(?:\\.\\d+)?)\\s*(' + sortedTmp + ').*?$', 'i'); 258 | 259 | return { 260 | list: list, 261 | map: map, 262 | re: re, 263 | base: unitBase, 264 | }; 265 | } 266 | 267 | /** 268 | * Binary search to find the greatest index which has a value <=. 269 | * 270 | * @function findPrefix 271 | * @private 272 | * @param {Array} list the scale's units list 273 | * @param {Number} value a numeric value 274 | * @returns {Array} the found list item 275 | */ 276 | function findPrefix(list: any[], value: number): any[] { 277 | /* jshint bitwise: false */ 278 | 279 | let low = 0; 280 | let high = list.length - 1; 281 | 282 | let mid, current; 283 | while (low !== high) { 284 | mid = (low + high + 1) >> 1; 285 | current = list[mid][1]; 286 | 287 | if (current > value) { 288 | high = mid - 1; 289 | } else { 290 | low = mid; 291 | } 292 | } 293 | 294 | return list[low]; 295 | } 296 | 297 | /** 298 | * Format the given value according to a scale and return it 299 | * 300 | * @function _numberScale 301 | * @see defaultOptions 302 | * @param {Number} num the number to format to scale 303 | * @param {Object} options the options 304 | * @returns {string} the scaled number 305 | */ 306 | function _numberScale(num: number, options: NumberScaleOptions): string | string[] { 307 | let value; 308 | let remainder; 309 | 310 | options = mergeDefaults(options); 311 | 312 | // Ensures `value` is a number (or NaN). 313 | value = Number(num); 314 | const scale = knownScales[options.scale]; 315 | 316 | // If `value` is 0 or NaN. 317 | if (!value) { 318 | return `0${scale.base}${(options.unit && ' ' + options.unit) || options.unit}`; 319 | } 320 | 321 | const neg = value < 0; 322 | value = Math.abs(value); 323 | 324 | const prefix: number[] = findPrefix(scale.list, value); 325 | const roundModifier = +`1e${Math.max(options.precision, 0)}`; 326 | 327 | value = round(options.roundMode as string, neg, (value * roundModifier) / prefix[1]) / roundModifier; 328 | 329 | if (options.recursive) { 330 | --options.recursive; 331 | 332 | value = value | 0; 333 | remainder = Math.abs(num) - value * prefix[1]; 334 | 335 | value = String(value); 336 | } else { 337 | value = formatPrecision(value, options.precision); 338 | } 339 | 340 | if (neg) { 341 | if (value !== '0') { 342 | value = '-' + value; 343 | } 344 | remainder = (remainder && -remainder) || 0; 345 | } 346 | const formattedUnit = `${prefix[0]}${options.unit}`; 347 | 348 | value = `${value}${formattedUnit ? ' ' : ''}${formattedUnit}`; 349 | 350 | if (remainder && prefix !== scale.list[0]) { 351 | remainder = _numberScale(remainder, options); 352 | 353 | if (Array.isArray(remainder)) { 354 | value = [value].concat(remainder); 355 | } else { 356 | value = [value, remainder]; 357 | } 358 | } 359 | 360 | return value; 361 | } 362 | 363 | /** 364 | * Parse this value and return a number, or NaN if value is invalid 365 | * 366 | * @function parseScale 367 | * @see defaultOptions 368 | * @param {(Array|Number)} value a value as returned by _numberScale() 369 | * @param {Object} options the options. Same as _numberScale()'s options 370 | * @return {(Number|NaN)} the parsed value as a number 371 | */ 372 | function parseScale(value: string | string[], options: NumberScaleOptions): number { 373 | let returnedValue: number; 374 | 375 | if (Array.isArray(value)) { 376 | return value.reduce(function (prev, val) { 377 | return prev + parseScale(val, options); 378 | }, 0); 379 | } 380 | 381 | options = mergeDefaults(options); 382 | 383 | const scale = knownScales[options.scale]; 384 | 385 | const matches = scale.re.exec(`${value}`); 386 | 387 | if (matches) { 388 | if (!matches[2]) { 389 | matches[2] = `${scale.base}`; 390 | } 391 | 392 | returnedValue = (matches as any)[1] * scale.map[matches[2]]; 393 | } else { 394 | returnedValue = NaN; 395 | } 396 | 397 | return returnedValue; 398 | } 399 | 400 | /** 401 | * Define and bind a new scale, or override an existing one 402 | * 403 | * @function defineScale 404 | * @param {string} name the scale name 405 | * @param {Object} prefixes the prefixes definitions 406 | * @param {Number} baseUnitValue the base value, or scale pivot point 407 | */ 408 | function defineScale(name: string, prefixes: ScalePrefixDefinition, baseUnitValue: number) { 409 | knownScales[name] = buildScale(prefixes, baseUnitValue); 410 | numberScale.scales[name] = knownScales[name]; 411 | } 412 | 413 | // Scale Aliasses 414 | knownScales['IEEE-1541'] = knownScales.IEEE1541; 415 | 416 | export type NumberScaleProtoype = (num: number, options: NumberScaleOptions) => string | string[]; 417 | export interface NumberScale extends NumberScaleProtoype { 418 | scales: Record; 419 | defineScale: typeof defineScale; 420 | parse: typeof parseScale; 421 | } 422 | 423 | /** 424 | * expose (readonly) API 425 | * 426 | * @namespace _numberScale 427 | * @borrows defineScale as defineScale 428 | * @borrows parseScale as scale 429 | * 430 | * @property {Object} options.default default options 431 | * @property {Object} scales all known scales 432 | * @property {Function} defineScale 433 | * @property {Function} parse 434 | */ 435 | export const numberScale: NumberScale = Object.defineProperties(_numberScale, { 436 | options: { 437 | configurable: false, 438 | enumerable: true, 439 | writable: false, 440 | value: { 441 | default: defaultOptions, 442 | }, 443 | }, 444 | scales: { 445 | configurable: false, 446 | enumerable: true, 447 | writable: false, 448 | value: Object.keys(knownScales).reduce(function (scales, scale) { 449 | scales[scale] = scale; 450 | return scales; 451 | }, {}), 452 | }, 453 | defineScale: { 454 | configurable: false, 455 | enumerable: true, 456 | writable: false, 457 | value: defineScale, 458 | }, 459 | parse: { 460 | configurable: false, 461 | enumerable: true, 462 | writable: false, 463 | value: parseScale, 464 | }, 465 | }); 466 | -------------------------------------------------------------------------------- /test/compliance.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-call */ 2 | import { readdirSync, statSync, readFileSync } from 'fs'; 3 | import { basename } from 'path'; 4 | import { search } from '../src'; 5 | 6 | // Compliance tests that aren't supported yet. 7 | const notImplementedYet = []; 8 | 9 | function endsWith(str, suffix) { 10 | return str.indexOf(suffix, str.length - suffix.length) !== -1; 11 | } 12 | 13 | const listing = readdirSync('test/compliance'); 14 | for (let i = 0; i < listing.length; i++) { 15 | const filename = 'test/compliance/' + listing[i]; 16 | if (statSync(filename).isFile() && endsWith(filename, '.json') && !notImplementedYet.includes(basename(filename))) { 17 | addTestSuitesFromFile(filename); 18 | } 19 | } 20 | 21 | /** 22 | * 23 | * @param {string} filename 24 | */ 25 | function addTestSuitesFromFile(filename) { 26 | describe(filename, () => { 27 | const spec = JSON.parse(readFileSync(filename, 'utf-8')); 28 | for (let i = 0; i < spec.length; i++) { 29 | const msg = `suite ${i} for filename ${filename}`; 30 | describe(msg, () => { 31 | const given = spec[i].given; 32 | const cases = spec[i].cases.map(c => [c.expression, c.result, c.error]); 33 | 34 | test.each(cases)('should pass test %# %s', (expression, result, error) => { 35 | if (error !== undefined) { 36 | expect(() => search(given, expression)).toThrow(error); 37 | } else { 38 | expect(search(given, expression)).toEqual(result); 39 | } 40 | }); 41 | }); 42 | } 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /test/compliance/basic.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "given": { 4 | "foo": { 5 | "bar": { 6 | "baz": "correct" 7 | } 8 | } 9 | }, 10 | "cases": [ 11 | { 12 | "expression": "foo", 13 | "result": { 14 | "bar": { 15 | "baz": "correct" 16 | } 17 | } 18 | }, 19 | { 20 | "expression": "foo.bar", 21 | "result": { 22 | "baz": "correct" 23 | } 24 | }, 25 | { 26 | "expression": "foo.bar.baz", 27 | "result": "correct" 28 | }, 29 | { 30 | "expression": "foo\n.\nbar\n.baz", 31 | "result": "correct" 32 | }, 33 | { 34 | "expression": "foo.bar.baz.bad", 35 | "result": null 36 | }, 37 | { 38 | "expression": "foo.bar.bad", 39 | "result": null 40 | }, 41 | { 42 | "expression": "foo.bad", 43 | "result": null 44 | }, 45 | { 46 | "expression": "bad", 47 | "result": null 48 | }, 49 | { 50 | "expression": "bad.morebad.morebad", 51 | "result": null 52 | } 53 | ] 54 | }, 55 | { 56 | "given": { 57 | "foo": { 58 | "bar": [ 59 | "one", 60 | "two", 61 | "three" 62 | ] 63 | } 64 | }, 65 | "cases": [ 66 | { 67 | "expression": "foo", 68 | "result": { 69 | "bar": [ 70 | "one", 71 | "two", 72 | "three" 73 | ] 74 | } 75 | }, 76 | { 77 | "expression": "foo.bar", 78 | "result": [ 79 | "one", 80 | "two", 81 | "three" 82 | ] 83 | } 84 | ] 85 | }, 86 | { 87 | "given": [ 88 | "one", 89 | "two", 90 | "three" 91 | ], 92 | "cases": [ 93 | { 94 | "expression": "one", 95 | "result": null 96 | }, 97 | { 98 | "expression": "two", 99 | "result": null 100 | }, 101 | { 102 | "expression": "three", 103 | "result": null 104 | }, 105 | { 106 | "expression": "one.two", 107 | "result": null 108 | } 109 | ] 110 | }, 111 | { 112 | "given": { 113 | "foo": { 114 | "1": [ 115 | "one", 116 | "two", 117 | "three" 118 | ], 119 | "-1": "bar" 120 | } 121 | }, 122 | "cases": [ 123 | { 124 | "expression": "foo.\"1\"", 125 | "result": [ 126 | "one", 127 | "two", 128 | "three" 129 | ] 130 | }, 131 | { 132 | "expression": "foo.\"1\"[0]", 133 | "result": "one" 134 | }, 135 | { 136 | "expression": "foo.\"-1\"", 137 | "result": "bar" 138 | } 139 | ] 140 | } 141 | ] 142 | -------------------------------------------------------------------------------- /test/compliance/boolean.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "given": { 4 | "outer": { 5 | "foo": "foo", 6 | "bar": "bar", 7 | "baz": "baz" 8 | } 9 | }, 10 | "cases": [ 11 | { 12 | "expression": "outer.foo || outer.bar", 13 | "result": "foo" 14 | }, 15 | { 16 | "expression": "outer.foo||outer.bar", 17 | "result": "foo" 18 | }, 19 | { 20 | "expression": "outer.bar || outer.baz", 21 | "result": "bar" 22 | }, 23 | { 24 | "expression": "outer.bar||outer.baz", 25 | "result": "bar" 26 | }, 27 | { 28 | "expression": "outer.bad || outer.foo", 29 | "result": "foo" 30 | }, 31 | { 32 | "expression": "outer.bad||outer.foo", 33 | "result": "foo" 34 | }, 35 | { 36 | "expression": "outer.foo || outer.bad", 37 | "result": "foo" 38 | }, 39 | { 40 | "expression": "outer.foo||outer.bad", 41 | "result": "foo" 42 | }, 43 | { 44 | "expression": "outer.bad || outer.alsobad", 45 | "result": null 46 | }, 47 | { 48 | "expression": "outer.bad||outer.alsobad", 49 | "result": null 50 | } 51 | ] 52 | }, 53 | { 54 | "given": { 55 | "outer": { 56 | "foo": "foo", 57 | "bool": false, 58 | "empty_list": [], 59 | "empty_string": "" 60 | } 61 | }, 62 | "cases": [ 63 | { 64 | "expression": "outer.empty_string || outer.foo", 65 | "result": "foo" 66 | }, 67 | { 68 | "expression": "outer.nokey || outer.bool || outer.empty_list || outer.empty_string || outer.foo", 69 | "result": "foo" 70 | } 71 | ] 72 | }, 73 | { 74 | "given": { 75 | "True": true, 76 | "False": false, 77 | "Number": 5, 78 | "EmptyList": [], 79 | "Zero": 0 80 | }, 81 | "cases": [ 82 | { 83 | "expression": "True && False", 84 | "result": false 85 | }, 86 | { 87 | "expression": "False && True", 88 | "result": false 89 | }, 90 | { 91 | "expression": "True && True", 92 | "result": true 93 | }, 94 | { 95 | "expression": "False && False", 96 | "result": false 97 | }, 98 | { 99 | "expression": "True && Number", 100 | "result": 5 101 | }, 102 | { 103 | "expression": "Number && True", 104 | "result": true 105 | }, 106 | { 107 | "expression": "Number && False", 108 | "result": false 109 | }, 110 | { 111 | "expression": "Number && EmptyList", 112 | "result": [] 113 | }, 114 | { 115 | "expression": "Number && True", 116 | "result": true 117 | }, 118 | { 119 | "expression": "EmptyList && True", 120 | "result": [] 121 | }, 122 | { 123 | "expression": "EmptyList && False", 124 | "result": [] 125 | }, 126 | { 127 | "expression": "True || False", 128 | "result": true 129 | }, 130 | { 131 | "expression": "True || True", 132 | "result": true 133 | }, 134 | { 135 | "expression": "False || True", 136 | "result": true 137 | }, 138 | { 139 | "expression": "False || False", 140 | "result": false 141 | }, 142 | { 143 | "expression": "Number || EmptyList", 144 | "result": 5 145 | }, 146 | { 147 | "expression": "Number || True", 148 | "result": 5 149 | }, 150 | { 151 | "expression": "Number || True && False", 152 | "result": 5 153 | }, 154 | { 155 | "expression": "(Number || True) && False", 156 | "result": false 157 | }, 158 | { 159 | "expression": "Number || (True && False)", 160 | "result": 5 161 | }, 162 | { 163 | "expression": "!True", 164 | "result": false 165 | }, 166 | { 167 | "expression": "!False", 168 | "result": true 169 | }, 170 | { 171 | "expression": "!Number", 172 | "result": false 173 | }, 174 | { 175 | "expression": "!EmptyList", 176 | "result": true 177 | }, 178 | { 179 | "expression": "True && !False", 180 | "result": true 181 | }, 182 | { 183 | "expression": "True && !EmptyList", 184 | "result": true 185 | }, 186 | { 187 | "expression": "!False && !EmptyList", 188 | "result": true 189 | }, 190 | { 191 | "expression": "!(True && False)", 192 | "result": true 193 | }, 194 | { 195 | "expression": "!Zero", 196 | "result": false 197 | }, 198 | { 199 | "expression": "!!Zero", 200 | "result": true 201 | } 202 | ] 203 | }, 204 | { 205 | "given": { 206 | "one": 1, 207 | "two": 2, 208 | "three": 3 209 | }, 210 | "cases": [ 211 | { 212 | "expression": "one < two", 213 | "result": true 214 | }, 215 | { 216 | "expression": "one <= two", 217 | "result": true 218 | }, 219 | { 220 | "expression": "one == one", 221 | "result": true 222 | }, 223 | { 224 | "expression": "one == two", 225 | "result": false 226 | }, 227 | { 228 | "expression": "one > two", 229 | "result": false 230 | }, 231 | { 232 | "expression": "one >= two", 233 | "result": false 234 | }, 235 | { 236 | "expression": "one != two", 237 | "result": true 238 | }, 239 | { 240 | "expression": "one < two && three > one", 241 | "result": true 242 | }, 243 | { 244 | "expression": "one < two || three > one", 245 | "result": true 246 | }, 247 | { 248 | "expression": "one < two || three < one", 249 | "result": true 250 | }, 251 | { 252 | "expression": "two < one || three < one", 253 | "result": false 254 | } 255 | ] 256 | } 257 | ] 258 | -------------------------------------------------------------------------------- /test/compliance/current.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "given": { 4 | "foo": [{"name": "a"}, {"name": "b"}], 5 | "bar": {"baz": "qux"} 6 | }, 7 | "cases": [ 8 | { 9 | "expression": "@", 10 | "result": { 11 | "foo": [{"name": "a"}, {"name": "b"}], 12 | "bar": {"baz": "qux"} 13 | } 14 | }, 15 | { 16 | "expression": "@.bar", 17 | "result": {"baz": "qux"} 18 | }, 19 | { 20 | "expression": "@.foo[0]", 21 | "result": {"name": "a"} 22 | } 23 | ] 24 | } 25 | ] 26 | -------------------------------------------------------------------------------- /test/compliance/escape.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "given": { 3 | "foo.bar": "dot", 4 | "foo bar": "space", 5 | "foo\nbar": "newline", 6 | "foo\"bar": "doublequote", 7 | "c:\\\\windows\\path": "windows", 8 | "/unix/path": "unix", 9 | "\"\"\"": "threequotes", 10 | "bar": {"baz": "qux"} 11 | }, 12 | "cases": [ 13 | { 14 | "expression": "\"foo.bar\"", 15 | "result": "dot" 16 | }, 17 | { 18 | "expression": "\"foo bar\"", 19 | "result": "space" 20 | }, 21 | { 22 | "expression": "\"foo\\nbar\"", 23 | "result": "newline" 24 | }, 25 | { 26 | "expression": "\"foo\\\"bar\"", 27 | "result": "doublequote" 28 | }, 29 | { 30 | "expression": "\"c:\\\\\\\\windows\\\\path\"", 31 | "result": "windows" 32 | }, 33 | { 34 | "expression": "\"/unix/path\"", 35 | "result": "unix" 36 | }, 37 | { 38 | "expression": "\"\\\"\\\"\\\"\"", 39 | "result": "threequotes" 40 | }, 41 | { 42 | "expression": "\"bar\".\"baz\"", 43 | "result": "qux" 44 | } 45 | ] 46 | }] 47 | -------------------------------------------------------------------------------- /test/compliance/filters.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "given": {"foo": [{"name": "a"}, {"name": "b"}]}, 4 | "cases": [ 5 | { 6 | "comment": "Matching a literal", 7 | "expression": "foo[?name == 'a']", 8 | "result": [{"name": "a"}] 9 | } 10 | ] 11 | }, 12 | { 13 | "given": {"foo": [0, 1], "bar": [2, 3]}, 14 | "cases": [ 15 | { 16 | "comment": "Matching a literal", 17 | "expression": "*[?[0] == `0`]", 18 | "result": [[], []] 19 | } 20 | ] 21 | }, 22 | { 23 | "given": {"foo": [{"first": "foo", "last": "bar"}, 24 | {"first": "foo", "last": "foo"}, 25 | {"first": "foo", "last": "baz"}]}, 26 | "cases": [ 27 | { 28 | "comment": "Matching an expression", 29 | "expression": "foo[?first == last]", 30 | "result": [{"first": "foo", "last": "foo"}] 31 | }, 32 | { 33 | "comment": "Verify projection created from filter", 34 | "expression": "foo[?first == last].first", 35 | "result": ["foo"] 36 | } 37 | ] 38 | }, 39 | { 40 | "given": {"foo": [{"age": 20}, 41 | {"age": 25}, 42 | {"age": 30}]}, 43 | "cases": [ 44 | { 45 | "comment": "Greater than with a number", 46 | "expression": "foo[?age > `25`]", 47 | "result": [{"age": 30}] 48 | }, 49 | { 50 | "expression": "foo[?age >= `25`]", 51 | "result": [{"age": 25}, {"age": 30}] 52 | }, 53 | { 54 | "comment": "Greater than with a number", 55 | "expression": "foo[?age > `30`]", 56 | "result": [] 57 | }, 58 | { 59 | "comment": "Greater than with a number", 60 | "expression": "foo[?age < `25`]", 61 | "result": [{"age": 20}] 62 | }, 63 | { 64 | "comment": "Greater than with a number", 65 | "expression": "foo[?age <= `25`]", 66 | "result": [{"age": 20}, {"age": 25}] 67 | }, 68 | { 69 | "comment": "Greater than with a number", 70 | "expression": "foo[?age < `20`]", 71 | "result": [] 72 | }, 73 | { 74 | "expression": "foo[?age == `20`]", 75 | "result": [{"age": 20}] 76 | }, 77 | { 78 | "expression": "foo[?age != `20`]", 79 | "result": [{"age": 25}, {"age": 30}] 80 | } 81 | ] 82 | }, 83 | { 84 | "given": {"foo": [{"top": {"name": "a"}}, 85 | {"top": {"name": "b"}}]}, 86 | "cases": [ 87 | { 88 | "comment": "Filter with subexpression", 89 | "expression": "foo[?top.name == 'a']", 90 | "result": [{"top": {"name": "a"}}] 91 | } 92 | ] 93 | }, 94 | { 95 | "given": {"foo": [{"top": {"first": "foo", "last": "bar"}}, 96 | {"top": {"first": "foo", "last": "foo"}}, 97 | {"top": {"first": "foo", "last": "baz"}}]}, 98 | "cases": [ 99 | { 100 | "comment": "Matching an expression", 101 | "expression": "foo[?top.first == top.last]", 102 | "result": [{"top": {"first": "foo", "last": "foo"}}] 103 | }, 104 | { 105 | "comment": "Matching a JSON array", 106 | "expression": "foo[?top == `{\"first\": \"foo\", \"last\": \"bar\"}`]", 107 | "result": [{"top": {"first": "foo", "last": "bar"}}] 108 | } 109 | ] 110 | }, 111 | { 112 | "given": {"foo": [ 113 | {"key": true}, 114 | {"key": false}, 115 | {"key": 0}, 116 | {"key": 1}, 117 | {"key": [0]}, 118 | {"key": {"bar": [0]}}, 119 | {"key": null}, 120 | {"key": [1]}, 121 | {"key": {"a":2}} 122 | ]}, 123 | "cases": [ 124 | { 125 | "expression": "foo[?key == `true`]", 126 | "result": [{"key": true}] 127 | }, 128 | { 129 | "expression": "foo[?key == `false`]", 130 | "result": [{"key": false}] 131 | }, 132 | { 133 | "expression": "foo[?key == `0`]", 134 | "result": [{"key": 0}] 135 | }, 136 | { 137 | "expression": "foo[?key == `1`]", 138 | "result": [{"key": 1}] 139 | }, 140 | { 141 | "expression": "foo[?key == `[0]`]", 142 | "result": [{"key": [0]}] 143 | }, 144 | { 145 | "expression": "foo[?key == `{\"bar\": [0]}`]", 146 | "result": [{"key": {"bar": [0]}}] 147 | }, 148 | { 149 | "expression": "foo[?key == `null`]", 150 | "result": [{"key": null}] 151 | }, 152 | { 153 | "expression": "foo[?key == `[1]`]", 154 | "result": [{"key": [1]}] 155 | }, 156 | { 157 | "expression": "foo[?key == `{\"a\":2}`]", 158 | "result": [{"key": {"a":2}}] 159 | }, 160 | { 161 | "expression": "foo[?`true` == key]", 162 | "result": [{"key": true}] 163 | }, 164 | { 165 | "expression": "foo[?`false` == key]", 166 | "result": [{"key": false}] 167 | }, 168 | { 169 | "expression": "foo[?`0` == key]", 170 | "result": [{"key": 0}] 171 | }, 172 | { 173 | "expression": "foo[?`1` == key]", 174 | "result": [{"key": 1}] 175 | }, 176 | { 177 | "expression": "foo[?`[0]` == key]", 178 | "result": [{"key": [0]}] 179 | }, 180 | { 181 | "expression": "foo[?`{\"bar\": [0]}` == key]", 182 | "result": [{"key": {"bar": [0]}}] 183 | }, 184 | { 185 | "expression": "foo[?`null` == key]", 186 | "result": [{"key": null}] 187 | }, 188 | { 189 | "expression": "foo[?`[1]` == key]", 190 | "result": [{"key": [1]}] 191 | }, 192 | { 193 | "expression": "foo[?`{\"a\":2}` == key]", 194 | "result": [{"key": {"a":2}}] 195 | }, 196 | { 197 | "expression": "foo[?key != `true`]", 198 | "result": [{"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, 199 | {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] 200 | }, 201 | { 202 | "expression": "foo[?key != `false`]", 203 | "result": [{"key": true}, {"key": 0}, {"key": 1}, {"key": [0]}, 204 | {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] 205 | }, 206 | { 207 | "expression": "foo[?key != `0`]", 208 | "result": [{"key": true}, {"key": false}, {"key": 1}, {"key": [0]}, 209 | {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] 210 | }, 211 | { 212 | "expression": "foo[?key != `1`]", 213 | "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": [0]}, 214 | {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] 215 | }, 216 | { 217 | "expression": "foo[?key != `null`]", 218 | "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, 219 | {"key": {"bar": [0]}}, {"key": [1]}, {"key": {"a":2}}] 220 | }, 221 | { 222 | "expression": "foo[?key != `[1]`]", 223 | "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, 224 | {"key": {"bar": [0]}}, {"key": null}, {"key": {"a":2}}] 225 | }, 226 | { 227 | "expression": "foo[?key != `{\"a\":2}`]", 228 | "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, 229 | {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}] 230 | }, 231 | { 232 | "expression": "foo[?`true` != key]", 233 | "result": [{"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, 234 | {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] 235 | }, 236 | { 237 | "expression": "foo[?`false` != key]", 238 | "result": [{"key": true}, {"key": 0}, {"key": 1}, {"key": [0]}, 239 | {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] 240 | }, 241 | { 242 | "expression": "foo[?`0` != key]", 243 | "result": [{"key": true}, {"key": false}, {"key": 1}, {"key": [0]}, 244 | {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] 245 | }, 246 | { 247 | "expression": "foo[?`1` != key]", 248 | "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": [0]}, 249 | {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] 250 | }, 251 | { 252 | "expression": "foo[?`null` != key]", 253 | "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, 254 | {"key": {"bar": [0]}}, {"key": [1]}, {"key": {"a":2}}] 255 | }, 256 | { 257 | "expression": "foo[?`[1]` != key]", 258 | "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, 259 | {"key": {"bar": [0]}}, {"key": null}, {"key": {"a":2}}] 260 | }, 261 | { 262 | "expression": "foo[?`{\"a\":2}` != key]", 263 | "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, 264 | {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}] 265 | } 266 | ] 267 | }, 268 | { 269 | "given": {"reservations": [ 270 | {"instances": [ 271 | {"foo": 1, "bar": 2}, {"foo": 1, "bar": 3}, 272 | {"foo": 1, "bar": 2}, {"foo": 2, "bar": 1}]}]}, 273 | "cases": [ 274 | { 275 | "expression": "reservations[].instances[?bar==`1`]", 276 | "result": [[{"foo": 2, "bar": 1}]] 277 | }, 278 | { 279 | "expression": "reservations[*].instances[?bar==`1`]", 280 | "result": [[{"foo": 2, "bar": 1}]] 281 | }, 282 | { 283 | "expression": "reservations[].instances[?bar==`1`][]", 284 | "result": [{"foo": 2, "bar": 1}] 285 | } 286 | ] 287 | }, 288 | { 289 | "given": { 290 | "baz": "other", 291 | "foo": [ 292 | {"bar": 1}, {"bar": 2}, {"bar": 3}, {"bar": 4}, {"bar": 1, "baz": 2} 293 | ] 294 | }, 295 | "cases": [ 296 | { 297 | "expression": "foo[?bar==`1`].bar[0]", 298 | "result": [] 299 | } 300 | ] 301 | }, 302 | { 303 | "given": { 304 | "foo": [ 305 | {"a": 1, "b": {"c": "x"}}, 306 | {"a": 1, "b": {"c": "y"}}, 307 | {"a": 1, "b": {"c": "z"}}, 308 | {"a": 2, "b": {"c": "z"}}, 309 | {"a": 1, "baz": 2} 310 | ] 311 | }, 312 | "cases": [ 313 | { 314 | "expression": "foo[?a==`1`].b.c", 315 | "result": ["x", "y", "z"] 316 | } 317 | ] 318 | }, 319 | { 320 | "given": {"foo": [{"name": "a"}, {"name": "b"}, {"name": "c"}]}, 321 | "cases": [ 322 | { 323 | "comment": "Filter with or expression", 324 | "expression": "foo[?name == 'a' || name == 'b']", 325 | "result": [{"name": "a"}, {"name": "b"}] 326 | }, 327 | { 328 | "expression": "foo[?name == 'a' || name == 'e']", 329 | "result": [{"name": "a"}] 330 | }, 331 | { 332 | "expression": "foo[?name == 'a' || name == 'b' || name == 'c']", 333 | "result": [{"name": "a"}, {"name": "b"}, {"name": "c"}] 334 | } 335 | ] 336 | }, 337 | { 338 | "given": {"foo": [{"a": 1, "b": 2}, {"a": 1, "b": 3}]}, 339 | "cases": [ 340 | { 341 | "comment": "Filter with and expression", 342 | "expression": "foo[?a == `1` && b == `2`]", 343 | "result": [{"a": 1, "b": 2}] 344 | }, 345 | { 346 | "expression": "foo[?a == `1` && b == `4`]", 347 | "result": [] 348 | } 349 | ] 350 | }, 351 | { 352 | "given": {"foo": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]}, 353 | "cases": [ 354 | { 355 | "comment": "Filter with Or and And expressions", 356 | "expression": "foo[?c == `3` || a == `1` && b == `4`]", 357 | "result": [{"a": 1, "b": 2, "c": 3}] 358 | }, 359 | { 360 | "expression": "foo[?b == `2` || a == `3` && b == `4`]", 361 | "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] 362 | }, 363 | { 364 | "expression": "foo[?a == `3` && b == `4` || b == `2`]", 365 | "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] 366 | }, 367 | { 368 | "expression": "foo[?(a == `3` && b == `4`) || b == `2`]", 369 | "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] 370 | }, 371 | { 372 | "expression": "foo[?((a == `3` && b == `4`)) || b == `2`]", 373 | "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] 374 | }, 375 | { 376 | "expression": "foo[?a == `3` && (b == `4` || b == `2`)]", 377 | "result": [{"a": 3, "b": 4}] 378 | }, 379 | { 380 | "expression": "foo[?a == `3` && ((b == `4` || b == `2`))]", 381 | "result": [{"a": 3, "b": 4}] 382 | } 383 | ] 384 | }, 385 | { 386 | "given": {"foo": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]}, 387 | "cases": [ 388 | { 389 | "comment": "Verify precedence of or/and expressions", 390 | "expression": "foo[?a == `1` || b ==`2` && c == `5`]", 391 | "result": [{"a": 1, "b": 2, "c": 3}] 392 | }, 393 | { 394 | "comment": "Parentheses can alter precedence", 395 | "expression": "foo[?(a == `1` || b ==`2`) && c == `5`]", 396 | "result": [] 397 | }, 398 | { 399 | "comment": "Not expressions combined with and/or", 400 | "expression": "foo[?!(a == `1` || b ==`2`)]", 401 | "result": [{"a": 3, "b": 4}] 402 | } 403 | ] 404 | }, 405 | { 406 | "given": { 407 | "foo": [ 408 | {"key": true}, 409 | {"key": false}, 410 | {"key": []}, 411 | {"key": {}}, 412 | {"key": [0]}, 413 | {"key": {"a": "b"}}, 414 | {"key": 0}, 415 | {"key": 1}, 416 | {"key": null}, 417 | {"notkey": true} 418 | ] 419 | }, 420 | "cases": [ 421 | { 422 | "comment": "Unary filter expression", 423 | "expression": "foo[?key]", 424 | "result": [ 425 | {"key": true}, {"key": [0]}, {"key": {"a": "b"}}, 426 | {"key": 0}, {"key": 1} 427 | ] 428 | }, 429 | { 430 | "comment": "Unary not filter expression", 431 | "expression": "foo[?!key]", 432 | "result": [ 433 | {"key": false}, {"key": []}, {"key": {}}, 434 | {"key": null}, {"notkey": true} 435 | ] 436 | }, 437 | { 438 | "comment": "Equality with null RHS", 439 | "expression": "foo[?key == `null`]", 440 | "result": [ 441 | {"key": null}, {"notkey": true} 442 | ] 443 | } 444 | ] 445 | }, 446 | { 447 | "given": { 448 | "foo": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 449 | }, 450 | "cases": [ 451 | { 452 | "comment": "Using @ in a filter expression", 453 | "expression": "foo[?@ < `5`]", 454 | "result": [0, 1, 2, 3, 4] 455 | }, 456 | { 457 | "comment": "Using @ in a filter expression", 458 | "expression": "foo[?`5` > @]", 459 | "result": [0, 1, 2, 3, 4] 460 | }, 461 | { 462 | "comment": "Using @ in a filter expression", 463 | "expression": "foo[?@ == @]", 464 | "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 465 | } 466 | ] 467 | } 468 | ] 469 | -------------------------------------------------------------------------------- /test/compliance/indices.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "given": 3 | {"foo": {"bar": ["zero", "one", "two"]}}, 4 | "cases": [ 5 | { 6 | "expression": "foo.bar[0]", 7 | "result": "zero" 8 | }, 9 | { 10 | "expression": "foo.bar[1]", 11 | "result": "one" 12 | }, 13 | { 14 | "expression": "foo.bar[2]", 15 | "result": "two" 16 | }, 17 | { 18 | "expression": "foo.bar[3]", 19 | "result": null 20 | }, 21 | { 22 | "expression": "foo.bar[-1]", 23 | "result": "two" 24 | }, 25 | { 26 | "expression": "foo.bar[-2]", 27 | "result": "one" 28 | }, 29 | { 30 | "expression": "foo.bar[-3]", 31 | "result": "zero" 32 | }, 33 | { 34 | "expression": "foo.bar[-4]", 35 | "result": null 36 | } 37 | ] 38 | }, 39 | { 40 | "given": 41 | {"foo": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]}, 42 | "cases": [ 43 | { 44 | "expression": "foo.bar", 45 | "result": null 46 | }, 47 | { 48 | "expression": "foo[0].bar", 49 | "result": "one" 50 | }, 51 | { 52 | "expression": "foo[1].bar", 53 | "result": "two" 54 | }, 55 | { 56 | "expression": "foo[2].bar", 57 | "result": "three" 58 | }, 59 | { 60 | "expression": "foo[3].notbar", 61 | "result": "four" 62 | }, 63 | { 64 | "expression": "foo[3].bar", 65 | "result": null 66 | }, 67 | { 68 | "expression": "foo[0]", 69 | "result": {"bar": "one"} 70 | }, 71 | { 72 | "expression": "foo[1]", 73 | "result": {"bar": "two"} 74 | }, 75 | { 76 | "expression": "foo[2]", 77 | "result": {"bar": "three"} 78 | }, 79 | { 80 | "expression": "foo[3]", 81 | "result": {"notbar": "four"} 82 | }, 83 | { 84 | "expression": "foo[4]", 85 | "result": null 86 | } 87 | ] 88 | }, 89 | { 90 | "given": [ 91 | "one", "two", "three" 92 | ], 93 | "cases": [ 94 | { 95 | "expression": "[0]", 96 | "result": "one" 97 | }, 98 | { 99 | "expression": "[1]", 100 | "result": "two" 101 | }, 102 | { 103 | "expression": "[2]", 104 | "result": "three" 105 | }, 106 | { 107 | "expression": "[-1]", 108 | "result": "three" 109 | }, 110 | { 111 | "expression": "[-2]", 112 | "result": "two" 113 | }, 114 | { 115 | "expression": "[-3]", 116 | "result": "one" 117 | } 118 | ] 119 | }, 120 | { 121 | "given": {"reservations": [ 122 | {"instances": [{"foo": 1}, {"foo": 2}]} 123 | ]}, 124 | "cases": [ 125 | { 126 | "expression": "reservations[].instances[].foo", 127 | "result": [1, 2] 128 | }, 129 | { 130 | "expression": "reservations[].instances[].bar", 131 | "result": [] 132 | }, 133 | { 134 | "expression": "reservations[].notinstances[].foo", 135 | "result": [] 136 | }, 137 | { 138 | "expression": "reservations[].notinstances[].foo", 139 | "result": [] 140 | } 141 | ] 142 | }, 143 | { 144 | "given": {"reservations": [{ 145 | "instances": [ 146 | {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]}, 147 | {"foo": [{"bar": 5}, {"bar": 6}, {"notbar": [7]}, {"bar": 8}]}, 148 | {"foo": "bar"}, 149 | {"notfoo": [{"bar": 20}, {"bar": 21}, {"notbar": [7]}, {"bar": 22}]}, 150 | {"bar": [{"baz": [1]}, {"baz": [2]}, {"baz": [3]}, {"baz": [4]}]}, 151 | {"baz": [{"baz": [1, 2]}, {"baz": []}, {"baz": []}, {"baz": [3, 4]}]}, 152 | {"qux": [{"baz": []}, {"baz": [1, 2, 3]}, {"baz": [4]}, {"baz": []}]} 153 | ], 154 | "otherkey": {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]} 155 | }, { 156 | "instances": [ 157 | {"a": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]}, 158 | {"b": [{"bar": 5}, {"bar": 6}, {"notbar": [7]}, {"bar": 8}]}, 159 | {"c": "bar"}, 160 | {"notfoo": [{"bar": 23}, {"bar": 24}, {"notbar": [7]}, {"bar": 25}]}, 161 | {"qux": [{"baz": []}, {"baz": [1, 2, 3]}, {"baz": [4]}, {"baz": []}]} 162 | ], 163 | "otherkey": {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]} 164 | } 165 | ]}, 166 | "cases": [ 167 | { 168 | "expression": "reservations[].instances[].foo[].bar", 169 | "result": [1, 2, 4, 5, 6, 8] 170 | }, 171 | { 172 | "expression": "reservations[].instances[].foo[].baz", 173 | "result": [] 174 | }, 175 | { 176 | "expression": "reservations[].instances[].notfoo[].bar", 177 | "result": [20, 21, 22, 23, 24, 25] 178 | }, 179 | { 180 | "expression": "reservations[].instances[].notfoo[].notbar", 181 | "result": [[7], [7]] 182 | }, 183 | { 184 | "expression": "reservations[].notinstances[].foo", 185 | "result": [] 186 | }, 187 | { 188 | "expression": "reservations[].instances[].foo[].notbar", 189 | "result": [3, [7]] 190 | }, 191 | { 192 | "expression": "reservations[].instances[].bar[].baz", 193 | "result": [[1], [2], [3], [4]] 194 | }, 195 | { 196 | "expression": "reservations[].instances[].baz[].baz", 197 | "result": [[1, 2], [], [], [3, 4]] 198 | }, 199 | { 200 | "expression": "reservations[].instances[].qux[].baz", 201 | "result": [[], [1, 2, 3], [4], [], [], [1, 2, 3], [4], []] 202 | }, 203 | { 204 | "expression": "reservations[].instances[].qux[].baz[]", 205 | "result": [1, 2, 3, 4, 1, 2, 3, 4] 206 | } 207 | ] 208 | }, 209 | { 210 | "given": { 211 | "foo": [ 212 | [["one", "two"], ["three", "four"]], 213 | [["five", "six"], ["seven", "eight"]], 214 | [["nine"], ["ten"]] 215 | ] 216 | }, 217 | "cases": [ 218 | { 219 | "expression": "foo[]", 220 | "result": [["one", "two"], ["three", "four"], ["five", "six"], 221 | ["seven", "eight"], ["nine"], ["ten"]] 222 | }, 223 | { 224 | "expression": "foo[][0]", 225 | "result": ["one", "three", "five", "seven", "nine", "ten"] 226 | }, 227 | { 228 | "expression": "foo[][1]", 229 | "result": ["two", "four", "six", "eight"] 230 | }, 231 | { 232 | "expression": "foo[][0][0]", 233 | "result": [] 234 | }, 235 | { 236 | "expression": "foo[][2][2]", 237 | "result": [] 238 | }, 239 | { 240 | "expression": "foo[][0][0][100]", 241 | "result": [] 242 | } 243 | ] 244 | }, 245 | { 246 | "given": { 247 | "foo": [{ 248 | "bar": [ 249 | { 250 | "qux": 2, 251 | "baz": 1 252 | }, 253 | { 254 | "qux": 4, 255 | "baz": 3 256 | } 257 | ] 258 | }, 259 | { 260 | "bar": [ 261 | { 262 | "qux": 6, 263 | "baz": 5 264 | }, 265 | { 266 | "qux": 8, 267 | "baz": 7 268 | } 269 | ] 270 | } 271 | ] 272 | }, 273 | "cases": [ 274 | { 275 | "expression": "foo", 276 | "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, 277 | {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] 278 | }, 279 | { 280 | "expression": "foo[]", 281 | "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, 282 | {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] 283 | }, 284 | { 285 | "expression": "foo[].bar", 286 | "result": [[{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}], 287 | [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]] 288 | }, 289 | { 290 | "expression": "foo[].bar[]", 291 | "result": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}, 292 | {"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}] 293 | }, 294 | { 295 | "expression": "foo[].bar[].baz", 296 | "result": [1, 3, 5, 7] 297 | } 298 | ] 299 | }, 300 | { 301 | "given": { 302 | "string": "string", 303 | "hash": {"foo": "bar", "bar": "baz"}, 304 | "number": 23, 305 | "nullvalue": null 306 | }, 307 | "cases": [ 308 | { 309 | "expression": "string[]", 310 | "result": null 311 | }, 312 | { 313 | "expression": "hash[]", 314 | "result": null 315 | }, 316 | { 317 | "expression": "number[]", 318 | "result": null 319 | }, 320 | { 321 | "expression": "nullvalue[]", 322 | "result": null 323 | }, 324 | { 325 | "expression": "string[].foo", 326 | "result": null 327 | }, 328 | { 329 | "expression": "hash[].foo", 330 | "result": null 331 | }, 332 | { 333 | "expression": "number[].foo", 334 | "result": null 335 | }, 336 | { 337 | "expression": "nullvalue[].foo", 338 | "result": null 339 | }, 340 | { 341 | "expression": "nullvalue[].foo[].bar", 342 | "result": null 343 | } 344 | ] 345 | } 346 | ] 347 | -------------------------------------------------------------------------------- /test/compliance/literal.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "given": { 4 | "foo": [ 5 | { 6 | "name": "a" 7 | }, 8 | { 9 | "name": "b" 10 | } 11 | ], 12 | "bar": { 13 | "baz": "qux" 14 | } 15 | }, 16 | "cases": [ 17 | { 18 | "expression": "`\"foo\"`", 19 | "result": "foo" 20 | }, 21 | { 22 | "comment": "Interpret escaped unicode.", 23 | "expression": "`\"\\u03a6\"`", 24 | "result": "Φ" 25 | }, 26 | { 27 | "expression": "`\"✓\"`", 28 | "result": "✓" 29 | }, 30 | { 31 | "expression": "`[1, 2, 3]`", 32 | "result": [ 33 | 1, 34 | 2, 35 | 3 36 | ] 37 | }, 38 | { 39 | "expression": "`{\"a\": \"b\"}`", 40 | "result": { 41 | "a": "b" 42 | } 43 | }, 44 | { 45 | "expression": "`true`", 46 | "result": true 47 | }, 48 | { 49 | "expression": "`false`", 50 | "result": false 51 | }, 52 | { 53 | "expression": "`null`", 54 | "result": null 55 | }, 56 | { 57 | "expression": "`0`", 58 | "result": 0 59 | }, 60 | { 61 | "expression": "`1`", 62 | "result": 1 63 | }, 64 | { 65 | "expression": "`2`", 66 | "result": 2 67 | }, 68 | { 69 | "expression": "`3`", 70 | "result": 3 71 | }, 72 | { 73 | "expression": "`4`", 74 | "result": 4 75 | }, 76 | { 77 | "expression": "`5`", 78 | "result": 5 79 | }, 80 | { 81 | "expression": "`6`", 82 | "result": 6 83 | }, 84 | { 85 | "expression": "`7`", 86 | "result": 7 87 | }, 88 | { 89 | "expression": "`8`", 90 | "result": 8 91 | }, 92 | { 93 | "expression": "`9`", 94 | "result": 9 95 | }, 96 | { 97 | "comment": "Escaping a backtick in quotes", 98 | "expression": "`\"foo\\`bar\"`", 99 | "result": "foo`bar" 100 | }, 101 | { 102 | "comment": "Double quote in literal", 103 | "expression": "`\"foo\\\"bar\"`", 104 | "result": "foo\"bar" 105 | }, 106 | { 107 | "expression": "`\"1\\`\"`", 108 | "result": "1`" 109 | }, 110 | { 111 | "comment": "Multiple literal expressions with escapes", 112 | "expression": "`\"\\\\\"`.{a:`\"b\"`}", 113 | "result": { 114 | "a": "b" 115 | } 116 | }, 117 | { 118 | "comment": "literal . identifier", 119 | "expression": "`{\"a\": \"b\"}`.a", 120 | "result": "b" 121 | }, 122 | { 123 | "comment": "literal . identifier . identifier", 124 | "expression": "`{\"a\": {\"b\": \"c\"}}`.a.b", 125 | "result": "c" 126 | }, 127 | { 128 | "comment": "literal . identifier bracket-expr", 129 | "expression": "`[0, 1, 2]`[1]", 130 | "result": 1 131 | } 132 | ] 133 | }, 134 | { 135 | "comment": "Literals", 136 | "given": { 137 | "type": "object" 138 | }, 139 | "cases": [ 140 | { 141 | "comment": "Literal with leading whitespace", 142 | "expression": "` {\"foo\": true}`", 143 | "result": { 144 | "foo": true 145 | } 146 | }, 147 | { 148 | "comment": "Literal with trailing whitespace", 149 | "expression": "`{\"foo\": true} `", 150 | "result": { 151 | "foo": true 152 | } 153 | }, 154 | { 155 | "comment": "Literal on RHS of subexpr not allowed", 156 | "expression": "foo.`\"bar\"`", 157 | "error": "Syntax error, unexpected token: bar(Literal)" 158 | } 159 | ] 160 | }, 161 | { 162 | "comment": "Raw String Literals", 163 | "given": {}, 164 | "cases": [ 165 | { 166 | "expression": "'foo'", 167 | "result": "foo" 168 | }, 169 | { 170 | "expression": "' foo '", 171 | "result": " foo " 172 | }, 173 | { 174 | "expression": "'0'", 175 | "result": "0" 176 | }, 177 | { 178 | "expression": "'newline\n'", 179 | "result": "newline\n" 180 | }, 181 | { 182 | "expression": "'\n'", 183 | "result": "\n" 184 | }, 185 | { 186 | "expression": "'✓'", 187 | "result": "✓" 188 | }, 189 | { 190 | "expression": "'𝄞'", 191 | "result": "𝄞" 192 | }, 193 | { 194 | "expression": "' [foo] '", 195 | "result": " [foo] " 196 | }, 197 | { 198 | "expression": "'[foo]'", 199 | "result": "[foo]" 200 | }, 201 | { 202 | "comment": "Do not interpret escaped unicode.", 203 | "expression": "'\\u03a6'", 204 | "result": "\\u03a6" 205 | }, 206 | { 207 | "comment": "Can escape the single quote", 208 | "expression": "'foo\\'bar'", 209 | "result": "foo'bar" 210 | } 211 | ] 212 | } 213 | ] 214 | -------------------------------------------------------------------------------- /test/compliance/multiselect.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "given": { 3 | "foo": { 4 | "bar": "bar", 5 | "baz": "baz", 6 | "qux": "qux", 7 | "nested": { 8 | "one": { 9 | "a": "first", 10 | "b": "second", 11 | "c": "third" 12 | }, 13 | "two": { 14 | "a": "first", 15 | "b": "second", 16 | "c": "third" 17 | }, 18 | "three": { 19 | "a": "first", 20 | "b": "second", 21 | "c": {"inner": "third"} 22 | } 23 | } 24 | }, 25 | "bar": 1, 26 | "baz": 2, 27 | "qux\"": 3 28 | }, 29 | "cases": [ 30 | { 31 | "expression": "foo.{bar: bar}", 32 | "result": {"bar": "bar"} 33 | }, 34 | { 35 | "expression": "foo.{\"bar\": bar}", 36 | "result": {"bar": "bar"} 37 | }, 38 | { 39 | "expression": "foo.{\"foo.bar\": bar}", 40 | "result": {"foo.bar": "bar"} 41 | }, 42 | { 43 | "expression": "foo.{bar: bar, baz: baz}", 44 | "result": {"bar": "bar", "baz": "baz"} 45 | }, 46 | { 47 | "expression": "foo.{\"bar\": bar, \"baz\": baz}", 48 | "result": {"bar": "bar", "baz": "baz"} 49 | }, 50 | { 51 | "expression": "{\"baz\": baz, \"qux\\\"\": \"qux\\\"\"}", 52 | "result": {"baz": 2, "qux\"": 3} 53 | }, 54 | { 55 | "expression": "foo.{bar:bar,baz:baz}", 56 | "result": {"bar": "bar", "baz": "baz"} 57 | }, 58 | { 59 | "expression": "foo.{bar: bar,qux: qux}", 60 | "result": {"bar": "bar", "qux": "qux"} 61 | }, 62 | { 63 | "expression": "foo.{bar: bar, noexist: noexist}", 64 | "result": {"bar": "bar", "noexist": null} 65 | }, 66 | { 67 | "expression": "foo.{noexist: noexist, alsonoexist: alsonoexist}", 68 | "result": {"noexist": null, "alsonoexist": null} 69 | }, 70 | { 71 | "expression": "foo.badkey.{nokey: nokey, alsonokey: alsonokey}", 72 | "result": null 73 | }, 74 | { 75 | "expression": "foo.nested.*.{a: a,b: b}", 76 | "result": [{"a": "first", "b": "second"}, 77 | {"a": "first", "b": "second"}, 78 | {"a": "first", "b": "second"}] 79 | }, 80 | { 81 | "expression": "foo.nested.three.{a: a, cinner: c.inner}", 82 | "result": {"a": "first", "cinner": "third"} 83 | }, 84 | { 85 | "expression": "foo.nested.three.{a: a, c: c.inner.bad.key}", 86 | "result": {"a": "first", "c": null} 87 | }, 88 | { 89 | "expression": "foo.{a: nested.one.a, b: nested.two.b}", 90 | "result": {"a": "first", "b": "second"} 91 | }, 92 | { 93 | "expression": "{bar: bar, baz: baz}", 94 | "result": {"bar": 1, "baz": 2} 95 | }, 96 | { 97 | "expression": "{bar: bar}", 98 | "result": {"bar": 1} 99 | }, 100 | { 101 | "expression": "{otherkey: bar}", 102 | "result": {"otherkey": 1} 103 | }, 104 | { 105 | "expression": "{no: no, exist: exist}", 106 | "result": {"no": null, "exist": null} 107 | }, 108 | { 109 | "expression": "foo.[bar]", 110 | "result": ["bar"] 111 | }, 112 | { 113 | "expression": "foo.[bar,baz]", 114 | "result": ["bar", "baz"] 115 | }, 116 | { 117 | "expression": "foo.[bar,qux]", 118 | "result": ["bar", "qux"] 119 | }, 120 | { 121 | "expression": "foo.[bar,noexist]", 122 | "result": ["bar", null] 123 | }, 124 | { 125 | "expression": "foo.[noexist,alsonoexist]", 126 | "result": [null, null] 127 | } 128 | ] 129 | }, { 130 | "given": { 131 | "foo": {"bar": 1, "baz": [2, 3, 4]} 132 | }, 133 | "cases": [ 134 | { 135 | "expression": "foo.{bar:bar,baz:baz}", 136 | "result": {"bar": 1, "baz": [2, 3, 4]} 137 | }, 138 | { 139 | "expression": "foo.[bar,baz[0]]", 140 | "result": [1, 2] 141 | }, 142 | { 143 | "expression": "foo.[bar,baz[1]]", 144 | "result": [1, 3] 145 | }, 146 | { 147 | "expression": "foo.[bar,baz[2]]", 148 | "result": [1, 4] 149 | }, 150 | { 151 | "expression": "foo.[bar,baz[3]]", 152 | "result": [1, null] 153 | }, 154 | { 155 | "expression": "foo.[bar[0],baz[3]]", 156 | "result": [null, null] 157 | } 158 | ] 159 | }, { 160 | "given": { 161 | "foo": {"bar": 1, "baz": 2} 162 | }, 163 | "cases": [ 164 | { 165 | "expression": "foo.{bar: bar, baz: baz}", 166 | "result": {"bar": 1, "baz": 2} 167 | }, 168 | { 169 | "expression": "foo.[bar,baz]", 170 | "result": [1, 2] 171 | } 172 | ] 173 | }, { 174 | "given": { 175 | "foo": { 176 | "bar": {"baz": [{"common": "first", "one": 1}, 177 | {"common": "second", "two": 2}]}, 178 | "ignoreme": 1, 179 | "includeme": true 180 | } 181 | }, 182 | "cases": [ 183 | { 184 | "expression": "foo.{bar: bar.baz[1],includeme: includeme}", 185 | "result": {"bar": {"common": "second", "two": 2}, "includeme": true} 186 | }, 187 | { 188 | "expression": "foo.{\"bar.baz.two\": bar.baz[1].two, includeme: includeme}", 189 | "result": {"bar.baz.two": 2, "includeme": true} 190 | }, 191 | { 192 | "expression": "foo.[includeme, bar.baz[*].common]", 193 | "result": [true, ["first", "second"]] 194 | }, 195 | { 196 | "expression": "foo.[includeme, bar.baz[*].none]", 197 | "result": [true, []] 198 | }, 199 | { 200 | "expression": "foo.[includeme, bar.baz[].common]", 201 | "result": [true, ["first", "second"]] 202 | } 203 | ] 204 | }, { 205 | "given": { 206 | "reservations": [{ 207 | "instances": [ 208 | {"id": "id1", 209 | "name": "first"}, 210 | {"id": "id2", 211 | "name": "second"} 212 | ]}, { 213 | "instances": [ 214 | {"id": "id3", 215 | "name": "third"}, 216 | {"id": "id4", 217 | "name": "fourth"} 218 | ]} 219 | ]}, 220 | "cases": [ 221 | { 222 | "expression": "reservations[*].instances[*].{id: id, name: name}", 223 | "result": [[{"id": "id1", "name": "first"}, {"id": "id2", "name": "second"}], 224 | [{"id": "id3", "name": "third"}, {"id": "id4", "name": "fourth"}]] 225 | }, 226 | { 227 | "expression": "reservations[].instances[].{id: id, name: name}", 228 | "result": [{"id": "id1", "name": "first"}, 229 | {"id": "id2", "name": "second"}, 230 | {"id": "id3", "name": "third"}, 231 | {"id": "id4", "name": "fourth"}] 232 | }, 233 | { 234 | "expression": "reservations[].instances[].[id, name]", 235 | "result": [["id1", "first"], 236 | ["id2", "second"], 237 | ["id3", "third"], 238 | ["id4", "fourth"]] 239 | } 240 | ] 241 | }, 242 | { 243 | "given": { 244 | "foo": [{ 245 | "bar": [ 246 | { 247 | "qux": 2, 248 | "baz": 1 249 | }, 250 | { 251 | "qux": 4, 252 | "baz": 3 253 | } 254 | ] 255 | }, 256 | { 257 | "bar": [ 258 | { 259 | "qux": 6, 260 | "baz": 5 261 | }, 262 | { 263 | "qux": 8, 264 | "baz": 7 265 | } 266 | ] 267 | } 268 | ] 269 | }, 270 | "cases": [ 271 | { 272 | "expression": "foo", 273 | "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, 274 | {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] 275 | }, 276 | { 277 | "expression": "foo[]", 278 | "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, 279 | {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] 280 | }, 281 | { 282 | "expression": "foo[].bar", 283 | "result": [[{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}], 284 | [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]] 285 | }, 286 | { 287 | "expression": "foo[].bar[]", 288 | "result": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}, 289 | {"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}] 290 | }, 291 | { 292 | "expression": "foo[].bar[].[baz, qux]", 293 | "result": [[1, 2], [3, 4], [5, 6], [7, 8]] 294 | }, 295 | { 296 | "expression": "foo[].bar[].[baz]", 297 | "result": [[1], [3], [5], [7]] 298 | }, 299 | { 300 | "expression": "foo[].bar[].[baz, qux][]", 301 | "result": [1, 2, 3, 4, 5, 6, 7, 8] 302 | } 303 | ] 304 | }, 305 | { 306 | "given": { 307 | "foo": { 308 | "baz": [ 309 | { 310 | "bar": "abc" 311 | }, { 312 | "bar": "def" 313 | } 314 | ], 315 | "qux": ["zero"] 316 | } 317 | }, 318 | "cases": [ 319 | { 320 | "expression": "foo.[baz[*].bar, qux[0]]", 321 | "result": [["abc", "def"], "zero"] 322 | } 323 | ] 324 | }, 325 | { 326 | "given": { 327 | "foo": { 328 | "baz": [ 329 | { 330 | "bar": "a", 331 | "bam": "b", 332 | "boo": "c" 333 | }, { 334 | "bar": "d", 335 | "bam": "e", 336 | "boo": "f" 337 | } 338 | ], 339 | "qux": ["zero"] 340 | } 341 | }, 342 | "cases": [ 343 | { 344 | "expression": "foo.[baz[*].[bar, boo], qux[0]]", 345 | "result": [[["a", "c" ], ["d", "f" ]], "zero"] 346 | } 347 | ] 348 | }, 349 | { 350 | "given": { 351 | "foo": { 352 | "baz": [ 353 | { 354 | "bar": "a", 355 | "bam": "b", 356 | "boo": "c" 357 | }, { 358 | "bar": "d", 359 | "bam": "e", 360 | "boo": "f" 361 | } 362 | ], 363 | "qux": ["zero"] 364 | } 365 | }, 366 | "cases": [ 367 | { 368 | "expression": "foo.[baz[*].not_there || baz[*].bar, qux[0]]", 369 | "result": [["a", "d"], "zero"] 370 | } 371 | ] 372 | }, 373 | { 374 | "given": {"type": "object"}, 375 | "cases": [ 376 | { 377 | "comment": "Nested multiselect", 378 | "expression": "[[*],*]", 379 | "result": [null, ["object"]] 380 | } 381 | ] 382 | }, 383 | { 384 | "given": [], 385 | "cases": [ 386 | { 387 | "comment": "Nested multiselect", 388 | "expression": "[[*]]", 389 | "result": [[]] 390 | } 391 | ] 392 | } 393 | ] 394 | -------------------------------------------------------------------------------- /test/compliance/pipe.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "given": { 3 | "foo": { 4 | "bar": { 5 | "baz": "subkey" 6 | }, 7 | "other": { 8 | "baz": "subkey" 9 | }, 10 | "other2": { 11 | "baz": "subkey" 12 | }, 13 | "other3": { 14 | "notbaz": ["a", "b", "c"] 15 | }, 16 | "other4": { 17 | "notbaz": ["a", "b", "c"] 18 | } 19 | } 20 | }, 21 | "cases": [ 22 | { 23 | "expression": "foo.*.baz | [0]", 24 | "result": "subkey" 25 | }, 26 | { 27 | "expression": "foo.*.baz | [1]", 28 | "result": "subkey" 29 | }, 30 | { 31 | "expression": "foo.*.baz | [2]", 32 | "result": "subkey" 33 | }, 34 | { 35 | "expression": "foo.bar.* | [0]", 36 | "result": "subkey" 37 | }, 38 | { 39 | "expression": "foo.*.notbaz | [*]", 40 | "result": [["a", "b", "c"], ["a", "b", "c"]] 41 | }, 42 | { 43 | "expression": "{\"a\": foo.bar, \"b\": foo.other} | *.baz", 44 | "result": ["subkey", "subkey"] 45 | } 46 | ] 47 | }, { 48 | "given": { 49 | "foo": { 50 | "bar": { 51 | "baz": "one" 52 | }, 53 | "other": { 54 | "baz": "two" 55 | }, 56 | "other2": { 57 | "baz": "three" 58 | }, 59 | "other3": { 60 | "notbaz": ["a", "b", "c"] 61 | }, 62 | "other4": { 63 | "notbaz": ["d", "e", "f"] 64 | } 65 | } 66 | }, 67 | "cases": [ 68 | { 69 | "expression": "foo | bar", 70 | "result": {"baz": "one"} 71 | }, 72 | { 73 | "expression": "foo | bar | baz", 74 | "result": "one" 75 | }, 76 | { 77 | "expression": "foo|bar| baz", 78 | "result": "one" 79 | }, 80 | { 81 | "expression": "not_there | [0]", 82 | "result": null 83 | }, 84 | { 85 | "expression": "not_there | [0]", 86 | "result": null 87 | }, 88 | { 89 | "expression": "[foo.bar, foo.other] | [0]", 90 | "result": {"baz": "one"} 91 | }, 92 | { 93 | "expression": "{\"a\": foo.bar, \"b\": foo.other} | a", 94 | "result": {"baz": "one"} 95 | }, 96 | { 97 | "expression": "{\"a\": foo.bar, \"b\": foo.other} | b", 98 | "result": {"baz": "two"} 99 | }, 100 | { 101 | "expression": "foo.bam || foo.bar | baz", 102 | "result": "one" 103 | }, 104 | { 105 | "expression": "foo | not_there || bar", 106 | "result": {"baz": "one"} 107 | } 108 | ] 109 | }, { 110 | "given": { 111 | "foo": [{ 112 | "bar": [{ 113 | "baz": "one" 114 | }, { 115 | "baz": "two" 116 | }] 117 | }, { 118 | "bar": [{ 119 | "baz": "three" 120 | }, { 121 | "baz": "four" 122 | }] 123 | }] 124 | }, 125 | "cases": [ 126 | { 127 | "expression": "foo[*].bar[*] | [0][0]", 128 | "result": {"baz": "one"} 129 | } 130 | ] 131 | }] 132 | -------------------------------------------------------------------------------- /test/compliance/slice.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "given": { 3 | "foo": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 4 | "bar": { 5 | "baz": 1 6 | } 7 | }, 8 | "cases": [ 9 | { 10 | "expression": "bar[0:10]", 11 | "result": null 12 | }, 13 | { 14 | "expression": "foo[0:10:1]", 15 | "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 16 | }, 17 | { 18 | "expression": "foo[0:10]", 19 | "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 20 | }, 21 | { 22 | "expression": "foo[0:10:]", 23 | "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 24 | }, 25 | { 26 | "expression": "foo[0::1]", 27 | "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 28 | }, 29 | { 30 | "expression": "foo[0::]", 31 | "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 32 | }, 33 | { 34 | "expression": "foo[0:]", 35 | "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 36 | }, 37 | { 38 | "expression": "foo[:10:1]", 39 | "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 40 | }, 41 | { 42 | "expression": "foo[::1]", 43 | "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 44 | }, 45 | { 46 | "expression": "foo[:10:]", 47 | "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 48 | }, 49 | { 50 | "expression": "foo[::]", 51 | "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 52 | }, 53 | { 54 | "expression": "foo[:]", 55 | "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 56 | }, 57 | { 58 | "expression": "foo[1:9]", 59 | "result": [1, 2, 3, 4, 5, 6, 7, 8] 60 | }, 61 | { 62 | "expression": "foo[0:10:2]", 63 | "result": [0, 2, 4, 6, 8] 64 | }, 65 | { 66 | "expression": "foo[5:]", 67 | "result": [5, 6, 7, 8, 9] 68 | }, 69 | { 70 | "expression": "foo[5::2]", 71 | "result": [5, 7, 9] 72 | }, 73 | { 74 | "expression": "foo[::2]", 75 | "result": [0, 2, 4, 6, 8] 76 | }, 77 | { 78 | "expression": "foo[::-1]", 79 | "result": [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] 80 | }, 81 | { 82 | "expression": "foo[1::2]", 83 | "result": [1, 3, 5, 7, 9] 84 | }, 85 | { 86 | "expression": "foo[10:0:-1]", 87 | "result": [9, 8, 7, 6, 5, 4, 3, 2, 1] 88 | }, 89 | { 90 | "expression": "foo[10:5:-1]", 91 | "result": [9, 8, 7, 6] 92 | }, 93 | { 94 | "expression": "foo[8:2:-2]", 95 | "result": [8, 6, 4] 96 | }, 97 | { 98 | "expression": "foo[0:20]", 99 | "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 100 | }, 101 | { 102 | "expression": "foo[10:-20:-1]", 103 | "result": [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] 104 | }, 105 | { 106 | "expression": "foo[10:-20]", 107 | "result": [] 108 | }, 109 | { 110 | "expression": "foo[-4:-1]", 111 | "result": [6, 7, 8] 112 | }, 113 | { 114 | "expression": "foo[:-5:-1]", 115 | "result": [9, 8, 7, 6] 116 | }, 117 | { 118 | "expression": "foo[8:2:0]", 119 | "error": "Invalid slice, step cannot be 0" 120 | }, 121 | { 122 | "expression": "foo[8:2:0:1]", 123 | "error": "Expected Rbracket, got: Number" 124 | }, 125 | { 126 | "expression": "foo[8:2&]", 127 | "error": "Syntax error, unexpected token: &(Expref)" 128 | }, 129 | { 130 | "expression": "foo[2:a:3]", 131 | "error": "Syntax error, unexpected token: a(UnquotedIdentifier)" 132 | } 133 | ] 134 | }, { 135 | "given": { 136 | "foo": [{"a": 1}, {"a": 2}, {"a": 3}], 137 | "bar": [{"a": {"b": 1}}, {"a": {"b": 2}}, 138 | {"a": {"b": 3}}], 139 | "baz": 50 140 | }, 141 | "cases": [ 142 | { 143 | "expression": "foo[:2].a", 144 | "result": [1, 2] 145 | }, 146 | { 147 | "expression": "foo[:2].b", 148 | "result": [] 149 | }, 150 | { 151 | "expression": "foo[:2].a.b", 152 | "result": [] 153 | }, 154 | { 155 | "expression": "bar[::-1].a.b", 156 | "result": [3, 2, 1] 157 | }, 158 | { 159 | "expression": "bar[:2].a.b", 160 | "result": [1, 2] 161 | }, 162 | { 163 | "expression": "baz[:2].a", 164 | "result": null 165 | } 166 | ] 167 | }, { 168 | "given": [{"a": 1}, {"a": 2}, {"a": 3}], 169 | "cases": [ 170 | { 171 | "expression": "[:]", 172 | "result": [{"a": 1}, {"a": 2}, {"a": 3}] 173 | }, 174 | { 175 | "expression": "[:2].a", 176 | "result": [1, 2] 177 | }, 178 | { 179 | "expression": "[::-1].a", 180 | "result": [3, 2, 1] 181 | }, 182 | { 183 | "expression": "[:2].b", 184 | "result": [] 185 | } 186 | ] 187 | }] 188 | -------------------------------------------------------------------------------- /test/compliance/unicode.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "given": {"foo": [{"✓": "✓"}, {"✓": "✗"}]}, 4 | "cases": [ 5 | { 6 | "expression": "foo[].\"✓\"", 7 | "result": ["✓", "✗"] 8 | } 9 | ] 10 | }, 11 | { 12 | "given": {"☯": true}, 13 | "cases": [ 14 | { 15 | "expression": "\"☯\"", 16 | "result": true 17 | } 18 | ] 19 | }, 20 | { 21 | "given": {"♪♫•*¨*•.¸¸❤¸¸.•*¨*•♫♪": true}, 22 | "cases": [ 23 | { 24 | "expression": "\"♪♫•*¨*•.¸¸❤¸¸.•*¨*•♫♪\"", 25 | "result": true 26 | } 27 | ] 28 | }, 29 | { 30 | "given": {"☃": true}, 31 | "cases": [ 32 | { 33 | "expression": "\"☃\"", 34 | "result": true 35 | } 36 | ] 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /test/compliance/wildcard.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "given": { 3 | "foo": { 4 | "bar": { 5 | "baz": "val" 6 | }, 7 | "other": { 8 | "baz": "val" 9 | }, 10 | "other2": { 11 | "baz": "val" 12 | }, 13 | "other3": { 14 | "notbaz": ["a", "b", "c"] 15 | }, 16 | "other4": { 17 | "notbaz": ["a", "b", "c"] 18 | }, 19 | "other5": { 20 | "other": { 21 | "a": 1, 22 | "b": 1, 23 | "c": 1 24 | } 25 | } 26 | } 27 | }, 28 | "cases": [ 29 | { 30 | "expression": "foo.*.baz", 31 | "result": ["val", "val", "val"] 32 | }, 33 | { 34 | "expression": "foo.bar.*", 35 | "result": ["val"] 36 | }, 37 | { 38 | "expression": "foo.*.notbaz", 39 | "result": [["a", "b", "c"], ["a", "b", "c"]] 40 | }, 41 | { 42 | "expression": "foo.*.notbaz[0]", 43 | "result": ["a", "a"] 44 | }, 45 | { 46 | "expression": "foo.*.notbaz[-1]", 47 | "result": ["c", "c"] 48 | } 49 | ] 50 | }, { 51 | "given": { 52 | "foo": { 53 | "first-1": { 54 | "second-1": "val" 55 | }, 56 | "first-2": { 57 | "second-1": "val" 58 | }, 59 | "first-3": { 60 | "second-1": "val" 61 | } 62 | } 63 | }, 64 | "cases": [ 65 | { 66 | "expression": "foo.*", 67 | "result": [{"second-1": "val"}, {"second-1": "val"}, 68 | {"second-1": "val"}] 69 | }, 70 | { 71 | "expression": "foo.*.*", 72 | "result": [["val"], ["val"], ["val"]] 73 | }, 74 | { 75 | "expression": "foo.*.*.*", 76 | "result": [[], [], []] 77 | }, 78 | { 79 | "expression": "foo.*.*.*.*", 80 | "result": [[], [], []] 81 | } 82 | ] 83 | }, { 84 | "given": { 85 | "foo": { 86 | "bar": "one" 87 | }, 88 | "other": { 89 | "bar": "one" 90 | }, 91 | "nomatch": { 92 | "notbar": "three" 93 | } 94 | }, 95 | "cases": [ 96 | { 97 | "expression": "*.bar", 98 | "result": ["one", "one"] 99 | } 100 | ] 101 | }, { 102 | "given": { 103 | "top1": { 104 | "sub1": {"foo": "one"} 105 | }, 106 | "top2": { 107 | "sub1": {"foo": "one"} 108 | } 109 | }, 110 | "cases": [ 111 | { 112 | "expression": "*", 113 | "result": [{"sub1": {"foo": "one"}}, 114 | {"sub1": {"foo": "one"}}] 115 | }, 116 | { 117 | "expression": "*.sub1", 118 | "result": [{"foo": "one"}, 119 | {"foo": "one"}] 120 | }, 121 | { 122 | "expression": "*.*", 123 | "result": [[{"foo": "one"}], 124 | [{"foo": "one"}]] 125 | }, 126 | { 127 | "expression": "*.*.foo[]", 128 | "result": ["one", "one"] 129 | }, 130 | { 131 | "expression": "*.sub1.foo", 132 | "result": ["one", "one"] 133 | } 134 | ] 135 | }, 136 | { 137 | "given": 138 | {"foo": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]}, 139 | "cases": [ 140 | { 141 | "expression": "foo[*].bar", 142 | "result": ["one", "two", "three"] 143 | }, 144 | { 145 | "expression": "foo[*].notbar", 146 | "result": ["four"] 147 | } 148 | ] 149 | }, 150 | { 151 | "given": 152 | [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}], 153 | "cases": [ 154 | { 155 | "expression": "[*]", 156 | "result": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}] 157 | }, 158 | { 159 | "expression": "[*].bar", 160 | "result": ["one", "two", "three"] 161 | }, 162 | { 163 | "expression": "[*].notbar", 164 | "result": ["four"] 165 | } 166 | ] 167 | }, 168 | { 169 | "given": { 170 | "foo": { 171 | "bar": [ 172 | {"baz": ["one", "two", "three"]}, 173 | {"baz": ["four", "five", "six"]}, 174 | {"baz": ["seven", "eight", "nine"]} 175 | ] 176 | } 177 | }, 178 | "cases": [ 179 | { 180 | "expression": "foo.bar[*].baz", 181 | "result": [["one", "two", "three"], ["four", "five", "six"], ["seven", "eight", "nine"]] 182 | }, 183 | { 184 | "expression": "foo.bar[*].baz[0]", 185 | "result": ["one", "four", "seven"] 186 | }, 187 | { 188 | "expression": "foo.bar[*].baz[1]", 189 | "result": ["two", "five", "eight"] 190 | }, 191 | { 192 | "expression": "foo.bar[*].baz[2]", 193 | "result": ["three", "six", "nine"] 194 | }, 195 | { 196 | "expression": "foo.bar[*].baz[3]", 197 | "result": [] 198 | } 199 | ] 200 | }, 201 | { 202 | "given": { 203 | "foo": { 204 | "bar": [["one", "two"], ["three", "four"]] 205 | } 206 | }, 207 | "cases": [ 208 | { 209 | "expression": "foo.bar[*]", 210 | "result": [["one", "two"], ["three", "four"]] 211 | }, 212 | { 213 | "expression": "foo.bar[0]", 214 | "result": ["one", "two"] 215 | }, 216 | { 217 | "expression": "foo.bar[0][0]", 218 | "result": "one" 219 | }, 220 | { 221 | "expression": "foo.bar[0][0][0]", 222 | "result": null 223 | }, 224 | { 225 | "expression": "foo.bar[0][0][0][0]", 226 | "result": null 227 | }, 228 | { 229 | "expression": "foo[0][0]", 230 | "result": null 231 | } 232 | ] 233 | }, 234 | { 235 | "given": { 236 | "foo": [ 237 | {"bar": [{"kind": "basic"}, {"kind": "intermediate"}]}, 238 | {"bar": [{"kind": "advanced"}, {"kind": "expert"}]}, 239 | {"bar": "string"} 240 | ] 241 | 242 | }, 243 | "cases": [ 244 | { 245 | "expression": "foo[*].bar[*].kind", 246 | "result": [["basic", "intermediate"], ["advanced", "expert"]] 247 | }, 248 | { 249 | "expression": "foo[*].bar[0].kind", 250 | "result": ["basic", "advanced"] 251 | } 252 | ] 253 | }, 254 | { 255 | "given": { 256 | "foo": [ 257 | {"bar": {"kind": "basic"}}, 258 | {"bar": {"kind": "intermediate"}}, 259 | {"bar": {"kind": "advanced"}}, 260 | {"bar": {"kind": "expert"}}, 261 | {"bar": "string"} 262 | ] 263 | }, 264 | "cases": [ 265 | { 266 | "expression": "foo[*].bar.kind", 267 | "result": ["basic", "intermediate", "advanced", "expert"] 268 | } 269 | ] 270 | }, 271 | { 272 | "given": { 273 | "foo": [{"bar": ["one", "two"]}, {"bar": ["three", "four"]}, {"bar": ["five"]}] 274 | }, 275 | "cases": [ 276 | { 277 | "expression": "foo[*].bar[0]", 278 | "result": ["one", "three", "five"] 279 | }, 280 | { 281 | "expression": "foo[*].bar[1]", 282 | "result": ["two", "four"] 283 | }, 284 | { 285 | "expression": "foo[*].bar[2]", 286 | "result": [] 287 | } 288 | ] 289 | }, 290 | { 291 | "given": { 292 | "foo": [{"bar": []}, {"bar": []}, {"bar": []}] 293 | }, 294 | "cases": [ 295 | { 296 | "expression": "foo[*].bar[0]", 297 | "result": [] 298 | } 299 | ] 300 | }, 301 | { 302 | "given": { 303 | "foo": [["one", "two"], ["three", "four"], ["five"]] 304 | }, 305 | "cases": [ 306 | { 307 | "expression": "foo[*][0]", 308 | "result": ["one", "three", "five"] 309 | }, 310 | { 311 | "expression": "foo[*][1]", 312 | "result": ["two", "four"] 313 | } 314 | ] 315 | }, 316 | { 317 | "given": { 318 | "foo": [ 319 | [ 320 | ["one", "two"], ["three", "four"] 321 | ], [ 322 | ["five", "six"], ["seven", "eight"] 323 | ], [ 324 | ["nine"], ["ten"] 325 | ] 326 | ] 327 | }, 328 | "cases": [ 329 | { 330 | "expression": "foo[*][0]", 331 | "result": [["one", "two"], ["five", "six"], ["nine"]] 332 | }, 333 | { 334 | "expression": "foo[*][1]", 335 | "result": [["three", "four"], ["seven", "eight"], ["ten"]] 336 | }, 337 | { 338 | "expression": "foo[*][0][0]", 339 | "result": ["one", "five", "nine"] 340 | }, 341 | { 342 | "expression": "foo[*][1][0]", 343 | "result": ["three", "seven", "ten"] 344 | }, 345 | { 346 | "expression": "foo[*][0][1]", 347 | "result": ["two", "six"] 348 | }, 349 | { 350 | "expression": "foo[*][1][1]", 351 | "result": ["four", "eight"] 352 | }, 353 | { 354 | "expression": "foo[*][2]", 355 | "result": [] 356 | }, 357 | { 358 | "expression": "foo[*][2][2]", 359 | "result": [] 360 | }, 361 | { 362 | "expression": "bar[*]", 363 | "result": null 364 | }, 365 | { 366 | "expression": "bar[*].baz[*]", 367 | "result": null 368 | } 369 | ] 370 | }, 371 | { 372 | "given": { 373 | "string": "string", 374 | "hash": {"foo": "bar", "bar": "baz"}, 375 | "number": 23, 376 | "nullvalue": null 377 | }, 378 | "cases": [ 379 | { 380 | "expression": "string[*]", 381 | "result": null 382 | }, 383 | { 384 | "expression": "hash[*]", 385 | "result": null 386 | }, 387 | { 388 | "expression": "number[*]", 389 | "result": null 390 | }, 391 | { 392 | "expression": "nullvalue[*]", 393 | "result": null 394 | }, 395 | { 396 | "expression": "string[*].foo", 397 | "result": null 398 | }, 399 | { 400 | "expression": "hash[*].foo", 401 | "result": null 402 | }, 403 | { 404 | "expression": "number[*].foo", 405 | "result": null 406 | }, 407 | { 408 | "expression": "nullvalue[*].foo", 409 | "result": null 410 | }, 411 | { 412 | "expression": "nullvalue[*].foo[*].bar", 413 | "result": null 414 | } 415 | ] 416 | }, 417 | { 418 | "given": { 419 | "string": "string", 420 | "hash": {"foo": "val", "bar": "val"}, 421 | "number": 23, 422 | "array": [1, 2, 3], 423 | "nullvalue": null 424 | }, 425 | "cases": [ 426 | { 427 | "expression": "string.*", 428 | "result": null 429 | }, 430 | { 431 | "expression": "hash.*", 432 | "result": ["val", "val"] 433 | }, 434 | { 435 | "expression": "number.*", 436 | "result": null 437 | }, 438 | { 439 | "expression": "array.*", 440 | "result": null 441 | }, 442 | { 443 | "expression": "nullvalue.*", 444 | "result": null 445 | } 446 | ] 447 | }, 448 | { 449 | "given": { 450 | "a": [0, 1, 2], 451 | "b": [0, 1, 2] 452 | }, 453 | "cases": [ 454 | { 455 | "expression": "*[0]", 456 | "result": [0, 0] 457 | } 458 | ] 459 | } 460 | ] 461 | -------------------------------------------------------------------------------- /test/jmespath.spec.ts: -------------------------------------------------------------------------------- 1 | import jmespath, { search, tokenize, compile, registerFunction, TreeInterpreter } from '../src'; 2 | 3 | describe('tokenize', () => { 4 | it('should tokenize unquoted identifier', () => { 5 | expect(tokenize('foo')).toMatchObject([{ type: 'UnquotedIdentifier', value: 'foo', start: 0 }]); 6 | }); 7 | it('should tokenize unquoted identifier with underscore', () => { 8 | expect(tokenize('_underscore')).toMatchObject([{ type: 'UnquotedIdentifier', value: '_underscore', start: 0 }]); 9 | }); 10 | it('should tokenize unquoted identifier with numbers', () => { 11 | expect(tokenize('foo123')).toMatchObject([{ type: 'UnquotedIdentifier', value: 'foo123', start: 0 }]); 12 | }); 13 | it('should tokenize dotted lookups', () => { 14 | expect(tokenize('foo.bar')).toMatchObject([ 15 | { type: 'UnquotedIdentifier', value: 'foo', start: 0 }, 16 | { type: 'Dot', value: '.', start: 3 }, 17 | { type: 'UnquotedIdentifier', value: 'bar', start: 4 }, 18 | ]); 19 | }); 20 | it('should tokenize numbers', () => { 21 | expect(tokenize('foo[0]')).toMatchObject([ 22 | { type: 'UnquotedIdentifier', value: 'foo', start: 0 }, 23 | { type: 'Lbracket', value: '[', start: 3 }, 24 | { type: 'Number', value: 0, start: 4 }, 25 | { type: 'Rbracket', value: ']', start: 5 }, 26 | ]); 27 | }); 28 | it('should tokenize numbers with multiple digits', () => { 29 | expect(tokenize('12345')).toMatchObject([{ type: 'Number', value: 12345, start: 0 }]); 30 | }); 31 | it('should tokenize negative numbers', () => { 32 | expect(tokenize('-12345')).toMatchObject([{ type: 'Number', value: -12345, start: 0 }]); 33 | }); 34 | it('should tokenize quoted identifier', () => { 35 | expect(tokenize('"foo"')).toMatchObject([{ type: 'QuotedIdentifier', value: 'foo', start: 0 }]); 36 | }); 37 | it('should tokenize quoted identifier with unicode escape', () => { 38 | expect(tokenize('"\\u2713"')).toMatchObject([{ type: 'QuotedIdentifier', value: '✓', start: 0 }]); 39 | }); 40 | it('should tokenize literal lists', () => { 41 | expect(tokenize('`[0, 1]`')).toMatchObject([{ type: 'Literal', value: [0, 1], start: 0 }]); 42 | }); 43 | it('should tokenize literal dict', () => { 44 | expect(tokenize('`{"foo": "bar"}`')).toMatchObject([{ type: 'Literal', value: { foo: 'bar' }, start: 0 }]); 45 | }); 46 | it('should tokenize literal strings', () => { 47 | expect(tokenize('`"foo"`')).toMatchObject([{ type: 'Literal', value: 'foo', start: 0 }]); 48 | }); 49 | it('should tokenize json literals', () => { 50 | expect(tokenize('`true`')).toMatchObject([{ type: 'Literal', value: true, start: 0 }]); 51 | }); 52 | it('should not requiring surrounding quotes for strings', () => { 53 | expect(tokenize('`foo`')).toMatchObject([{ type: 'Literal', value: 'foo', start: 0 }]); 54 | }); 55 | it('should not requiring surrounding quotes for numbers', () => { 56 | expect(tokenize('`20`')).toMatchObject([{ type: 'Literal', value: 20, start: 0 }]); 57 | }); 58 | it('should tokenize literal lists with chars afterwards', () => { 59 | expect(tokenize('`[0, 1]`[0]')).toMatchObject([ 60 | { type: 'Literal', value: [0, 1], start: 0 }, 61 | { type: 'Lbracket', value: '[', start: 8 }, 62 | { type: 'Number', value: 0, start: 9 }, 63 | { type: 'Rbracket', value: ']', start: 10 }, 64 | ]); 65 | }); 66 | it('should tokenize two char tokens with shared prefix', () => { 67 | expect(tokenize('[?foo]')).toMatchObject([ 68 | { type: 'Filter', value: '[?', start: 0 }, 69 | { type: 'UnquotedIdentifier', value: 'foo', start: 2 }, 70 | { type: 'Rbracket', value: ']', start: 5 }, 71 | ]); 72 | }); 73 | it('should tokenize flatten operator', () => { 74 | expect(tokenize('[]')).toMatchObject([{ type: 'Flatten', value: '[]', start: 0 }]); 75 | }); 76 | it('should tokenize comparators', () => { 77 | expect(tokenize('<')).toMatchObject([{ type: 'LT', value: '<', start: 0 }]); 78 | }); 79 | it('should tokenize two char tokens without shared prefix', () => { 80 | expect(tokenize('==')).toMatchObject([{ type: 'EQ', value: '==', start: 0 }]); 81 | }); 82 | it('should tokenize not equals', () => { 83 | expect(tokenize('!=')).toMatchObject([{ type: 'NE', value: '!=', start: 0 }]); 84 | }); 85 | it('should tokenize the OR token', () => { 86 | expect(tokenize('a||b')).toMatchObject([ 87 | { type: 'UnquotedIdentifier', value: 'a', start: 0 }, 88 | { type: 'Or', value: '||', start: 1 }, 89 | { type: 'UnquotedIdentifier', value: 'b', start: 3 }, 90 | ]); 91 | }); 92 | it('should tokenize function calls', () => { 93 | expect(tokenize('abs(@)')).toMatchObject([ 94 | { type: 'UnquotedIdentifier', value: 'abs', start: 0 }, 95 | { type: 'Lparen', value: '(', start: 3 }, 96 | { type: 'Current', value: '@', start: 4 }, 97 | { type: 'Rparen', value: ')', start: 5 }, 98 | ]); 99 | }); 100 | }); 101 | 102 | describe('parsing', () => { 103 | it('should parse field node', () => { 104 | expect(compile('foo')).toMatchObject({ type: 'Field', name: 'foo' }); 105 | }); 106 | }); 107 | 108 | describe('Searches compiled ast', () => { 109 | it('search a compiled expression', () => { 110 | const ast = compile('foo.bar'); 111 | expect(TreeInterpreter.search(ast, { foo: { bar: 'BAZ' } })).toEqual('BAZ'); 112 | }); 113 | }); 114 | 115 | describe('search', () => { 116 | it('should throw a readable error when invalid arguments are provided to a function', () => { 117 | try { 118 | search([], 'length(`null`)'); 119 | } catch (e) { 120 | expect(e.message).toContain('length() expected argument 1 to be type (string | array | object)'); 121 | expect(e.message).toContain('received type null instead.'); 122 | } 123 | }); 124 | }); 125 | 126 | describe('registerFunction', () => { 127 | it('register a customFunction', () => { 128 | expect(() => 129 | search( 130 | { 131 | foo: 60, 132 | bar: 10, 133 | }, 134 | 'modulus(foo, bar)', 135 | ), 136 | ).toThrow('Unknown function: modulus()'); 137 | jmespath.registerFunction( 138 | 'modulus', 139 | resolvedArgs => { 140 | const [dividend, divisor] = resolvedArgs; 141 | return dividend % divisor; 142 | }, 143 | [{ types: [jmespath.TYPE_NUMBER] }, { types: [jmespath.TYPE_NUMBER] }], 144 | ); 145 | expect(() => 146 | search( 147 | { 148 | foo: 6, 149 | bar: 7, 150 | }, 151 | 'modulus(foo, bar)', 152 | ), 153 | ).not.toThrow(); 154 | expect( 155 | search( 156 | { 157 | foo: 6, 158 | bar: 7, 159 | }, 160 | 'modulus(foo, bar)', 161 | ), 162 | ).toEqual(6); 163 | }); 164 | it("won't register a customFunction if one already exists", () => { 165 | expect(() => 166 | registerFunction( 167 | 'sum', 168 | () => { 169 | /* EMPTY */ 170 | }, 171 | [], 172 | ), 173 | ).toThrow('Function already defined: sum()'); 174 | }); 175 | }); 176 | -------------------------------------------------------------------------------- /test/lodash/array.spec.ts: -------------------------------------------------------------------------------- 1 | import { search } from '../../src'; 2 | 3 | describe('LODASH EXTENSIONS (ARRAY)', () => { 4 | it('handles `_` JMESPath function extension', () => { 5 | const returnValue = search( 6 | [ 7 | [1, 3, 5], 8 | [2, 4, 6], 9 | ], 10 | '@._zip([0], [1])', 11 | ); 12 | expect(returnValue).toStrictEqual([ 13 | [1, 2], 14 | [3, 4], 15 | [5, 6], 16 | ]); 17 | }); 18 | 19 | it('handles `_fromPairs` JMESPath function extension', () => { 20 | const returnValue = search( 21 | [ 22 | ['a', 1], 23 | ['b', 2], 24 | ], 25 | '_fromPairs(@)', 26 | ); 27 | expect(returnValue).toStrictEqual({ a: 1, b: 2 }); 28 | }); 29 | 30 | it('handles `_groupBy` JMESPath function extension', () => { 31 | const returnValue = search(['one', 'two', 'three'], '_groupBy(@, `length`)'); 32 | expect(returnValue).toStrictEqual({ 33 | 3: ['one', 'two'], 34 | 5: ['three'], 35 | }); 36 | }); 37 | 38 | it('handles `_maxBy` JMESPath function extension', () => { 39 | const returnValue = search([{ n: 1 }, { n: 2 }], '_maxBy(@, `n`)'); 40 | expect(returnValue).toStrictEqual({ 41 | n: 2, 42 | }); 43 | }); 44 | 45 | it('handles `_zip` JMESPath function extension', () => { 46 | const returnValue = search( 47 | [ 48 | ['a', 'b'], 49 | [1, 2], 50 | [true, false], 51 | ], 52 | '_zip([0], [1], [2])', 53 | ); 54 | expect(returnValue).toStrictEqual([ 55 | ['a', 1, true], 56 | ['b', 2, false], 57 | ]); 58 | }); 59 | 60 | it('handles `_chunk` JMESPath function extension', () => { 61 | const returnValue = search(['a', 'b', 'c', 'd'], '_chunk(@, `2`)'); 62 | expect(returnValue).toStrictEqual([ 63 | ['a', 'b'], 64 | ['c', 'd'], 65 | ]); 66 | }); 67 | 68 | it('handles `_compact` JMESPath function extension', () => { 69 | const returnValue = search([0, 1, false, 2, '', 3], '_compact(@)'); 70 | expect(returnValue).toStrictEqual([1, 2, 3]); 71 | }); 72 | 73 | it('handles `_concat` JMESPath function extension', () => { 74 | const returnValue = search([1], '_concat(@, `2`, [`3`], [[`4`]], `foo`)'); 75 | expect(returnValue).toStrictEqual([1, 2, 3, [4], 'foo']); 76 | expect(search([1, 2, 3], '_concat(@)')).toStrictEqual([1, 2, 3]); 77 | }); 78 | 79 | it('handles `_difference` JMESPath function extension', () => { 80 | const returnValue = search([2, 1], '_difference(@, [`2`, `3`])'); 81 | expect(returnValue).toStrictEqual([1]); 82 | expect(search([1, 2, 3], '_difference(@)')).toStrictEqual([1, 2, 3]); 83 | }); 84 | 85 | it('handles `_differenceBy` JMESPath function extension', () => { 86 | const returnValue = search([{ x: 2 }, { x: 1 }], '_differenceBy(@, [{ x: `1` }], `x`)'); 87 | expect(returnValue).toStrictEqual([{ x: 2 }]); 88 | }); 89 | 90 | it('handles `_drop` JMESPath function extension', () => { 91 | expect(search([1, 2, 3], '_drop(@)')).toStrictEqual([2, 3]); 92 | expect(search([1, 2, 3], '_drop(@, `2`)')).toStrictEqual([3]); 93 | expect(search([1, 2, 3], '_drop(@, `5`)')).toStrictEqual([]); 94 | expect(search([1, 2, 3], '_drop(@, `0`)')).toStrictEqual([1, 2, 3]); 95 | }); 96 | 97 | it('handles `_dropRight` JMESPath function extension', () => { 98 | expect(search([1, 2, 3], '_dropRight(@)')).toStrictEqual([1, 2]); 99 | expect(search([1, 2, 3], '_dropRight(@, `2`)')).toStrictEqual([1]); 100 | expect(search([1, 2, 3], '_dropRight(@, `5`)')).toStrictEqual([]); 101 | expect(search([1, 2, 3], '_dropRight(@, `0`)')).toStrictEqual([1, 2, 3]); 102 | }); 103 | 104 | it('handles `_dropRightWhile` JMESPath function extension', () => { 105 | const users = [ 106 | { user: 'barney', active: true }, 107 | { user: 'fred', active: false }, 108 | { user: 'pebbles', active: false }, 109 | ]; 110 | expect(search(users, '_dropRightWhile(@)')).toStrictEqual([]); 111 | expect(search(users, '_dropRightWhile(@, { user: `pebbles`, active: `false` })')).toStrictEqual([ 112 | { user: 'barney', active: true }, 113 | { user: 'fred', active: false }, 114 | ]); 115 | expect(search(users, "_dropRightWhile(@, ['active', `false`])")).toStrictEqual([{ user: 'barney', active: true }]); 116 | expect(search(users, "_dropRightWhile(@, 'active')")).toStrictEqual(users); 117 | }); 118 | 119 | it('handles `_dropWhile` JMESPath function extension', () => { 120 | const users = [ 121 | { user: 'barney', active: false }, 122 | { user: 'fred', active: false }, 123 | { user: 'pebbles', active: true }, 124 | ]; 125 | expect(search(users, '_dropWhile(@)')).toStrictEqual([]); 126 | expect(search(users, "_dropWhile(@, { user: 'barney', active: `false` })")).toStrictEqual([ 127 | { user: 'fred', active: false }, 128 | { user: 'pebbles', active: true }, 129 | ]); 130 | expect(search(users, "_dropWhile(@, ['active', `false`])")).toStrictEqual([{ user: 'pebbles', active: true }]); 131 | expect(search(users, "_dropWhile(@, 'active')")).toStrictEqual(users); 132 | }); 133 | 134 | it('handles `_fill` JMESPath function extension', () => { 135 | const array = [1, 2, 3]; 136 | expect(search(array, '_fill(@, `a`)')).toStrictEqual(['a', 'a', 'a']); 137 | expect(search(array, '_fill(@, `2`)')).toStrictEqual([2, 2, 2]); 138 | expect(search([4, 6, 8, 10], "_fill(@, '*', `1`, `3`)")).toStrictEqual([4, '*', '*', 10]); 139 | }); 140 | 141 | it('handles `_findIndex` JMESPath function extension', () => { 142 | const users = [ 143 | { user: 'barney', active: false }, 144 | { user: 'fred', active: false }, 145 | { user: 'pebbles', active: true }, 146 | ]; 147 | expect(search(users, '_findIndex(@)')).toStrictEqual(0); 148 | expect(search(users, "_findIndex(@, { user: 'fred', active: `false` })")).toStrictEqual(1); 149 | expect(search(users, "_findIndex(@, ['active', `false`])")).toStrictEqual(0); 150 | expect(search(users, "_findIndex(@, ['active', `false`], `1`)")).toStrictEqual(1); 151 | expect(search(users, "_findIndex(@, 'active')")).toStrictEqual(2); 152 | }); 153 | 154 | it('handles `_findLastIndex` JMESPath function extension', () => { 155 | const users = [ 156 | { user: 'barney', active: true }, 157 | { user: 'fred', active: false }, 158 | { user: 'pebbles', active: false }, 159 | ]; 160 | expect(search(users, '_findLastIndex(@)')).toStrictEqual(2); 161 | expect(search(users, "_findLastIndex(@, { user: 'barney', active: `true` })")).toStrictEqual(0); 162 | expect(search(users, "_findLastIndex(@, ['active', `false`])")).toStrictEqual(2); 163 | expect(search(users, "_findLastIndex(@, ['active', `false`], `1`)")).toStrictEqual(1); 164 | expect(search(users, "_findLastIndex(@, 'active')")).toStrictEqual(0); 165 | }); 166 | 167 | it('handles `_flatten` JMESPath function extension', () => { 168 | const array = [1, [2, [3, [4]], 5]]; 169 | expect(search(array, '_flatten(@)')).toStrictEqual([1, 2, [3, [4]], 5]); 170 | }); 171 | 172 | it('handles `_flattenDeep` JMESPath function extension', () => { 173 | const array = [1, [2, [3, [4]], 5]]; 174 | expect(search(array, '_flattenDeep(@)')).toStrictEqual([1, 2, 3, 4, 5]); 175 | }); 176 | 177 | it('handles `_flattenDepth` JMESPath function extension', () => { 178 | const array = [1, [2, [3, [4]], 5]]; 179 | expect(search(array, '_flattenDepth(@, `1`)')).toStrictEqual([1, 2, [3, [4]], 5]); 180 | expect(search(array, '_flattenDepth(@, `2`)')).toStrictEqual([1, 2, 3, [4], 5]); 181 | }); 182 | 183 | it('handles `_fromPairs` JMESPath function extension', () => { 184 | expect( 185 | search( 186 | [ 187 | ['a', 1], 188 | ['b', 2], 189 | ], 190 | '_fromPairs(@)', 191 | ), 192 | ).toStrictEqual({ a: 1, b: 2 }); 193 | }); 194 | 195 | it('handles `_first` JMESPath function extension', () => { 196 | expect(search([1, 2, 3], '_first(@)')).toStrictEqual(1); 197 | expect(search([], '_first(@)')).toStrictEqual(undefined); 198 | }); 199 | 200 | it('handles `_head` JMESPath function extension', () => { 201 | expect(search([1, 2, 3], '_head(@)')).toStrictEqual(1); 202 | expect(search([], '_head(@)')).toStrictEqual(undefined); 203 | }); 204 | 205 | it('handles `_indexOf` JMESPath function extension', () => { 206 | expect(search([1, 2, 1, 2], '_indexOf(@, `2`)')).toStrictEqual(1); 207 | expect(search([1, 2, 1, 2], '_indexOf(@, `2`, `2`)')).toStrictEqual(3); 208 | }); 209 | 210 | it('handles `_initial` JMESPath function extension', () => { 211 | expect(search([1, 2, 3], '_initial(@)')).toStrictEqual([1, 2]); 212 | }); 213 | 214 | it('handles `_intersection` JMESPath function extension', () => { 215 | expect( 216 | search( 217 | [ 218 | [2, 1], 219 | [2, 3], 220 | ], 221 | '_intersection([0],[1])', 222 | ), 223 | ).toStrictEqual([2]); 224 | }); 225 | 226 | it('handles `_join` JMESPath function extension', () => { 227 | expect(search([1, 2, 3], "_join(@, '~')")).toStrictEqual('1~2~3'); 228 | expect(search(['a', 'b', 'c'], "_join(@, '~')")).toStrictEqual('a~b~c'); 229 | }); 230 | 231 | it('handles `_last` JMESPath function extension', () => { 232 | expect(search([1, 2, 3], '_last(@)')).toStrictEqual(3); 233 | expect(search(['a', 'b', 'c'], '_last(@)')).toStrictEqual('c'); 234 | }); 235 | 236 | it('handles `_lastIndexOf` JMESPath function extension', () => { 237 | expect(search([1, 2, 1, 2], '_lastIndexOf(@, `2`)')).toStrictEqual(3); 238 | expect(search([1, 2, 1, 2], '_lastIndexOf(@, `2`, `2`)')).toStrictEqual(1); 239 | }); 240 | 241 | it('handles `_nth` JMESPath function extension', () => { 242 | const array = ['a', 'b', 'c', 'd']; 243 | expect(search(array, '_nth(@, `1`)')).toStrictEqual('b'); 244 | expect(search(array, '_nth(@, `-2`)')).toStrictEqual('c'); 245 | }); 246 | 247 | it('handles `_pull` JMESPath function extension', () => { 248 | const array = ['a', 'b', 'c', 'a', 'b', 'c']; 249 | expect(search(array, '_pull(@)')).toStrictEqual(array); 250 | expect(search(array, '_pull(@, `a`, `c`)')).toStrictEqual(['b', 'b']); 251 | }); 252 | 253 | it('handles `_pullAll` JMESPath function extension', () => { 254 | const array = ['a', 'b', 'c', 'a', 'b', 'c']; 255 | expect(search(array, '_pullAll(@)')).toStrictEqual(array); 256 | expect(search(array, '_pullAll(@, [`a`, `c`])')).toStrictEqual(['b', 'b']); 257 | }); 258 | 259 | it('handles `_pullAt` JMESPath function extension', () => { 260 | const array = ['a', 'b', 'c', 'd']; 261 | 262 | expect(search(array, '_pullAt(@)')).toStrictEqual([]); 263 | expect(search(array, '_pullAt(@, [`1`, `3`])')).toStrictEqual(['b', 'd']); 264 | expect(array).toStrictEqual(['a', 'c']); 265 | }); 266 | 267 | it('handles `_reverse` JMESPath function extension', () => { 268 | expect(search([1, 2, 3], '_reverse(@)')).toStrictEqual([3, 2, 1]); 269 | }); 270 | 271 | it('handles `_remove` JMESPath function extension', () => { 272 | const array = [1, 2, 3, 4]; 273 | const resultValue = search(array, "_remove(@, as_lambda('n => n % 2 === 0'))"); 274 | expect(array).toStrictEqual([1, 3]); 275 | expect(resultValue).toStrictEqual([2, 4]); 276 | }); 277 | 278 | it('handles `_slice` JMESPath function extension', () => { 279 | const array = ['a', 'b', 'c', 'a', 'b', 'c']; 280 | expect(search(array, '_slice(@, `2`, `5`)')).toStrictEqual(['c', 'a', 'b']); 281 | }); 282 | 283 | it('handles `_sortedIndex` JMESPath function extension', () => { 284 | const array = [30, 50]; 285 | expect(search(array, '_sortedIndex(@, `40`)')).toStrictEqual(1); 286 | }); 287 | 288 | it('handles `_sortedIndexBy` JMESPath function extension', () => { 289 | const objects = [{ x: 4 }, { x: 5 }]; 290 | expect(search(objects, "_sortedIndexBy(@, { x: `4` }, 'x')")).toStrictEqual(0); 291 | }); 292 | 293 | it('handles `_sortedIndexOf` JMESPath function extension', () => { 294 | const array = [4, 5, 5, 5, 6]; 295 | expect(search(array, '_sortedIndexOf(@, `5`)')).toStrictEqual(1); 296 | }); 297 | 298 | it('handles `_sortedLastIndex` JMESPath function extension', () => { 299 | const array = [4, 5, 5, 5, 6]; 300 | expect(search(array, '_sortedLastIndex(@, `5`)')).toStrictEqual(4); 301 | }); 302 | 303 | it('handles `_sortedLastIndexBy` JMESPath function extension', () => { 304 | const objects = [{ x: 3 }, { x: 5 }]; 305 | expect(search(objects, "_sortedIndexBy(@, { x: `4` }, 'x')")).toStrictEqual(1); 306 | }); 307 | 308 | it('handles `_sortedLastIndexOf` JMESPath function extension', () => { 309 | const array = [4, 5, 5, 5, 6]; 310 | expect(search(array, '_sortedLastIndexOf(@, `5`)')).toStrictEqual(3); 311 | }); 312 | 313 | it('handles `_sortedUniq` JMESPath function extension', () => { 314 | const array = [4, 5, 5, 6]; 315 | expect(search(array, '_sortedUniq(@)')).toStrictEqual([4, 5, 6]); 316 | }); 317 | 318 | it('handles `_tail` JMESPath function extension', () => { 319 | const array = [1, 2, 3]; 320 | expect(search(array, '_tail(@)')).toStrictEqual([2, 3]); 321 | }); 322 | 323 | it('handles `_take` JMESPath function extension', () => { 324 | const array = [1, 2, 3]; 325 | expect(search(array, '_take(@)')).toStrictEqual([1]); 326 | expect(search(array, '_take(@, `2`)')).toStrictEqual([1, 2]); 327 | expect(search(array, '_take(@, `5`)')).toStrictEqual([1, 2, 3]); 328 | expect(search(array, '_take(@, `0`)')).toStrictEqual([]); 329 | }); 330 | 331 | it('handles `_takeRight` JMESPath function extension', () => { 332 | const array = [1, 2, 3]; 333 | expect(search(array, '_takeRight(@)')).toStrictEqual([3]); 334 | expect(search(array, '_takeRight(@, `2`)')).toStrictEqual([2, 3]); 335 | expect(search(array, '_takeRight(@, `5`)')).toStrictEqual([1, 2, 3]); 336 | expect(search(array, '_takeRight(@, `0`)')).toStrictEqual([]); 337 | }); 338 | 339 | it('handles `_takeRightWhile` JMESPath function extension', () => { 340 | const users = [ 341 | { user: 'barney', active: true }, 342 | { user: 'fred', active: false }, 343 | { user: 'pebbles', active: false }, 344 | ]; 345 | expect(search(users, '_takeRightWhile(@)')).toStrictEqual([ 346 | { active: true, user: 'barney' }, 347 | { active: false, user: 'fred' }, 348 | { active: false, user: 'pebbles' }, 349 | ]); 350 | expect(search(users, "_takeRightWhile(@, { user: 'pebbles', active: `false` })")).toStrictEqual([ 351 | { user: 'pebbles', active: false }, 352 | ]); 353 | expect(search(users, "_takeRightWhile(@, ['active', `false`])")).toStrictEqual([ 354 | { active: false, user: 'fred' }, 355 | { active: false, user: 'pebbles' }, 356 | ]); 357 | expect(search(users, "_takeRightWhile(@, 'active')")).toStrictEqual([]); 358 | }); 359 | 360 | it('handles `_takeWhile` JMESPath function extension', () => { 361 | const users = [ 362 | { user: 'barney', active: false }, 363 | { user: 'fred', active: false }, 364 | { user: 'pebbles', active: true }, 365 | ]; 366 | expect(search(users, '_takeWhile(@)')).toStrictEqual([ 367 | { active: false, user: 'barney' }, 368 | { active: false, user: 'fred' }, 369 | { active: true, user: 'pebbles' }, 370 | ]); 371 | expect(search(users, "_takeWhile(@, { user: 'barney', active: `false` })")).toStrictEqual([ 372 | { active: false, user: 'barney' }, 373 | ]); 374 | expect(search(users, "_takeWhile(@, ['active', `false`])")).toStrictEqual([ 375 | { active: false, user: 'barney' }, 376 | { active: false, user: 'fred' }, 377 | ]); 378 | expect(search(users, "_takeWhile(@, 'active')")).toStrictEqual([]); 379 | }); 380 | 381 | it('handles `_union` JMESPath function extension', () => { 382 | const array = [2]; 383 | expect(search(array, '_union(@, [`1`, `2`])')).toStrictEqual([2, 1]); 384 | }); 385 | 386 | it('handles `_uniq` JMESPath function extension', () => { 387 | const array = [2, 1, 2]; 388 | expect(search(array, '_uniq(@)')).toStrictEqual([2, 1]); 389 | }); 390 | 391 | it('handles `_uniqBy` JMESPath function extension', () => { 392 | const array = [2, 1, 2]; 393 | expect(search(array, '_uniqBy(@)')).toStrictEqual([2, 1]); 394 | }); 395 | 396 | it('handles `_unzip` JMESPath function extension', () => { 397 | const array = [ 398 | ['a', 1, true], 399 | ['b', 2, false], 400 | ]; 401 | expect(search(array, '_unzip(@)')).toStrictEqual([ 402 | ['a', 'b'], 403 | [1, 2], 404 | [true, false], 405 | ]); 406 | }); 407 | 408 | it('handles `_without` JMESPath function extension', () => { 409 | const array = [2, 1, 2, 3]; 410 | expect(search(array, '_without(@, `1`, `2`)')).toStrictEqual([3]); 411 | }); 412 | 413 | it('handles `_xor` JMESPath function extension', () => { 414 | const array = [ 415 | [2, 1], 416 | [2, 3], 417 | ]; 418 | expect(search(array, '_xor([0], [1])')).toStrictEqual([1, 3]); 419 | }); 420 | 421 | it('handles `_zip` JMESPath function extension', () => { 422 | const array = [ 423 | ['a', 'b'], 424 | [1, 2], 425 | [true, false], 426 | ]; 427 | expect(search(array, '_zip([0], [1], [2])')).toStrictEqual([ 428 | ['a', 1, true], 429 | ['b', 2, false], 430 | ]); 431 | }); 432 | 433 | it('handles `_zipObject` JMESPath function extension', () => { 434 | const array = [ 435 | ['a', 'b'], 436 | [1, 2], 437 | ]; 438 | expect(search(array, '_zipObject([0], [1])')).toStrictEqual({ a: 1, b: 2 }); 439 | }); 440 | 441 | it('handles `_zipObjectDeep` JMESPath function extension', () => { 442 | const array = [ 443 | ['a.b[0].c', 'a.b[1].d'], 444 | [1, 2], 445 | ]; 446 | expect(search(array, '_zipObjectDeep([0], [1])')).toStrictEqual({ a: { b: [{ c: 1 }, { d: 2 }] } }); 447 | }); 448 | }); 449 | -------------------------------------------------------------------------------- /test/lodash/boilerplate.spec.ts: -------------------------------------------------------------------------------- 1 | import { search } from '../../src'; 2 | 3 | describe('LODASH EXTENSIONS (LANG)', () => { 4 | it('handles `_castArray` JMESPath function extension', () => { 5 | expect(search(1, '_castArray(@)')).toStrictEqual([1]); 6 | expect(search({ a: 1 }, '_castArray(@)')).toStrictEqual([{ a: 1 }]); 7 | expect(search('abc', '_castArray(@)')).toStrictEqual(['abc']); 8 | expect(search(null, '_castArray(@)')).toStrictEqual([null]); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/lodash/collection.spec.ts: -------------------------------------------------------------------------------- 1 | import { search } from '../../src'; 2 | 3 | describe('LODASH EXTENSIONS (COLLECTION)', () => { 4 | it('handles `_countBy` JMESPath function extension', () => { 5 | const array = ['one', 'two', 'three']; 6 | expect(search(array, "_countBy(@, 'length')")).toStrictEqual({ '3': 2, '5': 1 }); 7 | }); 8 | 9 | it('handles `_every` JMESPath function extension', () => { 10 | const array = [true, 1, null, 'yes']; 11 | const users = [ 12 | { user: 'barney', age: 36, active: false }, 13 | { user: 'fred', age: 40, active: false }, 14 | ]; 15 | expect(search(array, '_every(@, `true`)')).toStrictEqual(false); 16 | expect(search(users, "_every(@, { user: 'barney', active: `false` })")).toStrictEqual(false); 17 | expect(search(users, "_every(@, ['active', `false`])")).toStrictEqual(true); 18 | expect(search(users, "_every(@, 'active')")).toStrictEqual(false); 19 | }); 20 | 21 | it('handles `_filter` JMESPath function extension', () => { 22 | const users = [ 23 | { user: 'barney', age: 36, active: true }, 24 | { user: 'fred', age: 40, active: false }, 25 | ]; 26 | expect(search(users, '_filter(@, { age: `36`, active: `true` })')).toStrictEqual([ 27 | { 28 | user: 'barney', 29 | age: 36, 30 | active: true, 31 | }, 32 | ]); 33 | expect(search(users, "_filter(@, ['active', `false`])")).toStrictEqual([{ user: 'fred', age: 40, active: false }]); 34 | expect(search(users, "_filter(@, 'active')")).toStrictEqual([ 35 | { 36 | user: 'barney', 37 | age: 36, 38 | active: true, 39 | }, 40 | ]); 41 | }); 42 | 43 | it('handles `_find` JMESPath function extension', () => { 44 | const users = [ 45 | { user: 'barney', age: 36, active: true }, 46 | { user: 'fred', age: 40, active: false }, 47 | { user: 'pebbles', age: 1, active: true }, 48 | ]; 49 | expect(search(users, '_find(@, { age: `1`, active: `true` })')).toStrictEqual({ 50 | user: 'pebbles', 51 | age: 1, 52 | active: true, 53 | }); 54 | expect(search(users, "_find(@, as_lambda('x => x.age < 40'))")).toStrictEqual({ 55 | user: 'barney', 56 | age: 36, 57 | active: true, 58 | }); 59 | expect(search(users, "_find(@, ['active', `false`])")).toStrictEqual({ user: 'fred', age: 40, active: false }); 60 | expect(search(users, "_find(@, 'active')")).toStrictEqual({ 61 | user: 'barney', 62 | age: 36, 63 | active: true, 64 | }); 65 | }); 66 | 67 | it('handles `_findLast` JMESPath function extension', () => { 68 | const users = [ 69 | { user: 'barney', age: 36, active: true }, 70 | { user: 'fred', age: 40, active: false }, 71 | { user: 'pebbles', age: 1, active: true }, 72 | ]; 73 | expect(search(users, '_findLast(@, { age: `1`, active: `true` })')).toStrictEqual({ 74 | user: 'pebbles', 75 | age: 1, 76 | active: true, 77 | }); 78 | expect(search(users, "_findLast(@, ['active', `false`])")).toStrictEqual({ user: 'fred', age: 40, active: false }); 79 | expect(search(users, "_findLast(@, 'active')")).toStrictEqual({ 80 | user: 'pebbles', 81 | age: 1, 82 | active: true, 83 | }); 84 | }); 85 | 86 | it('handles `_flatMap` JMESPath function extension', () => { 87 | expect(search([1, 2], "_flatMap(@, as_lambda('x => [x, x]'))")).toStrictEqual([1, 1, 2, 2]); 88 | }); 89 | 90 | it('handles `_flatMapDeep` JMESPath function extension', () => { 91 | expect(search([1, 2], "_flatMapDeep(@, as_lambda('x => [[[x, x]]]'))")).toStrictEqual([1, 1, 2, 2]); 92 | }); 93 | 94 | it('handles `_forEach` JMESPath function extension', () => { 95 | expect(search([1, 2], "_forEach(@, as_lambda('x => x+2'))")).toStrictEqual([1, 2]); 96 | }); 97 | 98 | it('handles `_groupBy` JMESPath function extension', () => { 99 | expect(search([6.1, 4.2, 6.3], "_groupBy(@, as_lambda('Math.floor'))")).toStrictEqual({ 100 | '4': [4.2], 101 | '6': [6.1, 6.3], 102 | }); 103 | expect(search(['one', 'two', 'three'], "_groupBy(@, 'length')")).toStrictEqual({ 104 | '3': ['one', 'two'], 105 | '5': ['three'], 106 | }); 107 | }); 108 | 109 | it('handles `_includes` JMESPath function extension', () => { 110 | expect(search([1, 2, 3], '_includes(@, `1`)')).toStrictEqual(true); 111 | expect(search([1, 2, 3], '_includes(@, `1`, `2`)')).toStrictEqual(false); 112 | expect(search({ a: 1, b: 2 }, '_includes(@, `1`)')).toStrictEqual(true); 113 | expect(search('abcd', "_includes(@, 'bc')")).toStrictEqual(true); 114 | }); 115 | 116 | it('handles `_invokeMap` JMESPath function extension', () => { 117 | expect( 118 | search( 119 | [ 120 | [5, 1, 7], 121 | [3, 2, 1], 122 | ], 123 | "_invokeMap(@, 'sort')", 124 | ), 125 | ).toStrictEqual([ 126 | [1, 5, 7], 127 | [1, 2, 3], 128 | ]); 129 | }); 130 | 131 | it('handles `_keyBy` JMESPath function extension', () => { 132 | const array = [ 133 | { dir: 'left', code: 97 }, 134 | { dir: 'right', code: 100 }, 135 | ]; 136 | expect(search(array, "_keyBy(@, as_lambda('x => String.fromCharCode(x.code)'))")).toStrictEqual({ 137 | a: { dir: 'left', code: 97 }, 138 | d: { dir: 'right', code: 100 }, 139 | }); 140 | expect(search(array, "_keyBy(@, 'dir')")).toStrictEqual({ 141 | left: { dir: 'left', code: 97 }, 142 | right: { dir: 'right', code: 100 }, 143 | }); 144 | }); 145 | 146 | it('handles `_map` JMESPath function extension', () => { 147 | expect(search([4, 8], "_map(@, as_lambda('n => n * n'))")).toStrictEqual([16, 64]); 148 | expect(search({ a: 4, b: 8 }, "_map(@, as_lambda('n => n * n'))")).toStrictEqual([16, 64]); 149 | expect(search([{ user: 'barney' }, { user: 'fred' }], "_map(@, 'user')")).toStrictEqual(['barney', 'fred']); 150 | }); 151 | 152 | it('handles `_orderBy` JMESPath function extension', () => { 153 | const users = [ 154 | { user: 'fred', age: 48 }, 155 | { user: 'barney', age: 34 }, 156 | { user: 'fred', age: 40 }, 157 | { user: 'barney', age: 36 }, 158 | ]; 159 | expect(search(users, "_orderBy(@, ['user', 'age'], ['asc', 'desc'] )")).toStrictEqual([ 160 | { user: 'barney', age: 36 }, 161 | { user: 'barney', age: 34 }, 162 | { user: 'fred', age: 48 }, 163 | { user: 'fred', age: 40 }, 164 | ]); 165 | }); 166 | 167 | it('handles `_partition` JMESPath function extension', () => { 168 | const users = [ 169 | { user: 'barney', age: 36, active: false }, 170 | { user: 'fred', age: 40, active: true }, 171 | { user: 'pebbles', age: 1, active: false }, 172 | ]; 173 | expect(search(users, "_partition(@, as_lambda('o => o.active'))")).toStrictEqual([ 174 | [{ user: 'fred', age: 40, active: true }], 175 | [ 176 | { user: 'barney', age: 36, active: false }, 177 | { user: 'pebbles', age: 1, active: false }, 178 | ], 179 | ]); 180 | expect(search(users, '_partition(@, { age: `1`, active: `false` })')).toStrictEqual([ 181 | [{ user: 'pebbles', age: 1, active: false }], 182 | [ 183 | { user: 'barney', age: 36, active: false }, 184 | { user: 'fred', age: 40, active: true }, 185 | ], 186 | ]); 187 | expect(search(users, "_partition(@, ['active', `false`])")).toStrictEqual([ 188 | [ 189 | { user: 'barney', age: 36, active: false }, 190 | { user: 'pebbles', age: 1, active: false }, 191 | ], 192 | [{ user: 'fred', age: 40, active: true }], 193 | ]); 194 | expect(search(users, "_partition(@, 'active')")).toStrictEqual([ 195 | [{ user: 'fred', age: 40, active: true }], 196 | [ 197 | { user: 'barney', age: 36, active: false }, 198 | { user: 'pebbles', age: 1, active: false }, 199 | ], 200 | ]); 201 | }); 202 | 203 | it('handles `_reduce` JMESPath function extension', () => { 204 | const obj = { a: 1, b: 2, c: 1 }; 205 | expect( 206 | search(obj, "_reduce(@, as_lambda('(r, v, k) => {(r[v] || (r[v] = [])).push(k);return r;}'), `{}` )"), 207 | ).toStrictEqual({ '1': ['a', 'c'], '2': ['b'] }); 208 | }); 209 | 210 | it('handles `_reduceRight` JMESPath function extension', () => { 211 | const array = [ 212 | [0, 1], 213 | [2, 3], 214 | [4, 5], 215 | ]; 216 | expect( 217 | search(array, "_reduceRight(@, as_lambda('(flattened, other) => [...flattened, ...other]'), `[]` )"), 218 | ).toStrictEqual([4, 5, 2, 3, 0, 1]); 219 | }); 220 | 221 | it('handles `_reject` JMESPath function extension', () => { 222 | const users = [ 223 | { user: 'barney', age: 36, active: false }, 224 | { user: 'fred', age: 40, active: true }, 225 | ]; 226 | 227 | expect(search(users, "_reject(@, as_lambda('o => !o.active'))")).toStrictEqual([ 228 | { user: 'fred', age: 40, active: true }, 229 | ]); 230 | expect(search(users, '_reject(@, { age: `40`, active: `true` })')).toStrictEqual([ 231 | { user: 'barney', age: 36, active: false }, 232 | ]); 233 | expect(search(users, "_reject(@, ['active', `false`])")).toStrictEqual([{ user: 'fred', age: 40, active: true }]); 234 | expect(search(users, "_reject(@, 'active')")).toStrictEqual([{ user: 'barney', age: 36, active: false }]); 235 | }); 236 | 237 | it('handles `_sample` JMESPath function extension', () => { 238 | const result = search([1, 2, 3, 4], '_sample(@)') as number; 239 | expect([1, 2, 3, 4].includes(result)).toStrictEqual(true); 240 | }); 241 | 242 | it('handles `_sampleSize` JMESPath function extension', () => { 243 | let result = search([1, 2, 3], '_sampleSize(@, `2`)') as number[]; 244 | expect(result.length).toStrictEqual(2); 245 | expect(result.every(x => [1, 2, 3].includes(+x))).toStrictEqual(true); 246 | result = search([1, 2, 3], '_sampleSize(@, `4`)') as number[]; 247 | expect(result.length).toStrictEqual(3); 248 | expect(result.every(x => [1, 2, 3].includes(x))).toStrictEqual(true); 249 | }); 250 | 251 | it('handles `_shuffle` JMESPath function extension', () => { 252 | const result = search([1, 2, 3, 4], '_shuffle(@)') as number[]; 253 | expect(result.length).toStrictEqual(4); 254 | expect(result.every(x => [1, 2, 3, 4].includes(x))).toStrictEqual(true); 255 | }); 256 | 257 | it('handles `_size` JMESPath function extension', () => { 258 | expect(search([1, 2, 3], '_size(@)')).toStrictEqual(3); 259 | expect(search({ a: 1, b: 2 }, '_size(@)')).toStrictEqual(2); 260 | }); 261 | 262 | it('handles `_some` JMESPath function extension', () => { 263 | expect(search([null, 0, 'yes', false], "_some(@, as_lambda('Boolean'))")).toStrictEqual(true); 264 | 265 | const users = [ 266 | { user: 'barney', active: true }, 267 | { user: 'fred', active: false }, 268 | ]; 269 | 270 | expect(search(users, "_some(@, { user: 'barney', active: `false` })")).toStrictEqual(false); 271 | expect(search(users, "_some(@, ['active', `false`])")).toStrictEqual(true); 272 | expect(search(users, "_some(@, 'active')")).toStrictEqual(true); 273 | }); 274 | 275 | it('handles `_sortBy` JMESPath function extension', () => { 276 | const users = [ 277 | { user: 'fred', age: 48 }, 278 | { user: 'barney', age: 36 }, 279 | { user: 'fred', age: 40 }, 280 | { user: 'barney', age: 34 }, 281 | ]; 282 | 283 | expect(search(users, "_sortBy(@, as_lambda('o => o.user'))")).toStrictEqual([ 284 | { user: 'barney', age: 36 }, 285 | { user: 'barney', age: 34 }, 286 | { user: 'fred', age: 48 }, 287 | { user: 'fred', age: 40 }, 288 | ]); 289 | expect(search(users, "_sortBy(@, ['user', 'age'])")).toStrictEqual([ 290 | { user: 'barney', age: 34 }, 291 | { user: 'barney', age: 36 }, 292 | { user: 'fred', age: 40 }, 293 | { user: 'fred', age: 48 }, 294 | ]); 295 | }); 296 | }); 297 | -------------------------------------------------------------------------------- /test/lodash/lang.spec.ts: -------------------------------------------------------------------------------- 1 | import { search } from '../../src'; 2 | 3 | describe('LODASH EXTENSIONS (LANG)', () => { 4 | it('handles `_castArray` JMESPath function extension', () => { 5 | expect(search(1, '_castArray(@)')).toStrictEqual([1]); 6 | expect(search({ a: 1 }, '_castArray(@)')).toStrictEqual([{ a: 1 }]); 7 | expect(search('abc', '_castArray(@)')).toStrictEqual(['abc']); 8 | expect(search(null, '_castArray(`null`)')).toStrictEqual([null]); 9 | }); 10 | 11 | it('handles `_eq` JMESPath function extension', () => { 12 | const array = [{ a: 1 }, { a: 1 }]; 13 | expect(search(array, '_eq([0], [0])')).toStrictEqual(true); 14 | expect(search(array, '_eq([0], [1])')).toStrictEqual(false); 15 | expect(search(array, "_eq('a', 'a')")).toStrictEqual(true); 16 | expect(search(array, "_eq('a', as_lambda('() => Object(`a`)'))")).toStrictEqual(false); 17 | expect(search(array, '_eq(`null`, `null`)')).toStrictEqual(true); 18 | }); 19 | 20 | it('handles `_gt` JMESPath function extension', () => { 21 | const array = [{ a: 1 }, { a: 1 }]; 22 | expect(search(array, '_gt(`3`, `1`)')).toStrictEqual(true); 23 | expect(search(array, '_gt(`3`, `3`)')).toStrictEqual(false); 24 | expect(search(array, '_gt(`1`, `3`)')).toStrictEqual(false); 25 | }); 26 | 27 | it('handles `_gte` JMESPath function extension', () => { 28 | const array = [{ a: 1 }, { a: 1 }]; 29 | expect(search(array, '_gte(`3`, `1`)')).toStrictEqual(true); 30 | expect(search(array, '_gte(`3`, `3`)')).toStrictEqual(true); 31 | expect(search(array, '_gte(`1`, `3`)')).toStrictEqual(false); 32 | }); 33 | 34 | it('handles `_lt` JMESPath function extension', () => { 35 | const array = [{ a: 1 }, { a: 1 }]; 36 | expect(search(array, '_lt(`3`, `1`)')).toStrictEqual(false); 37 | expect(search(array, '_lt(`3`, `3`)')).toStrictEqual(false); 38 | expect(search(array, '_lt(`1`, `3`)')).toStrictEqual(true); 39 | }); 40 | 41 | it('handles `_lte` JMESPath function extension', () => { 42 | const array = [{ a: 1 }, { a: 1 }]; 43 | expect(search(array, '_lte(`3`, `1`)')).toStrictEqual(false); 44 | expect(search(array, '_lte(`3`, `3`)')).toStrictEqual(true); 45 | expect(search(array, '_lte(`1`, `3`)')).toStrictEqual(true); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/lodash/math.spec.ts: -------------------------------------------------------------------------------- 1 | import { search } from '../../src'; 2 | 3 | describe('LODASH EXTENSIONS (MATH)', () => { 4 | it('handles `_add` JMESPath function extension', () => { 5 | const array = [6, 4]; 6 | expect(search(array, '_add([0], [1])')).toStrictEqual(10); 7 | }); 8 | 9 | it('handles `_ceil` JMESPath function extension', () => { 10 | expect(search(4.006, '_ceil(@)')).toStrictEqual(5); 11 | expect(search(6.004, '_ceil(@, `2`)')).toStrictEqual(6.01); 12 | expect(search(6040, '_ceil(@, `-2`)')).toStrictEqual(6100); 13 | }); 14 | 15 | it('handles `_divide` JMESPath function extension', () => { 16 | const array = [6, 4]; 17 | expect(search(array, '_divide([0], [1])')).toStrictEqual(1.5); 18 | }); 19 | 20 | it('handles `_floor` JMESPath function extension', () => { 21 | expect(search(4.006, '_floor(@)')).toStrictEqual(4); 22 | expect(search(0.046, '_floor(@, `2`)')).toStrictEqual(0.04); 23 | expect(search(4060, '_floor(@, `-2`)')).toStrictEqual(4000); 24 | }); 25 | 26 | it('handles `_max` JMESPath function extension', () => { 27 | const array = [4, 2, 8, 6]; 28 | expect(search(array, '_max(@)')).toStrictEqual(8); 29 | expect(search([], '_max(@)')).toStrictEqual(undefined); 30 | }); 31 | 32 | it('handles `_maxBy` JMESPath function extension', () => { 33 | const objects = [{ n: 1 }, { n: 2 }]; 34 | expect(search(objects, "_maxBy(@, as_lambda('o => o.n'))")).toStrictEqual({ n: 2 }); 35 | expect(search(objects, "_maxBy(@, 'n')")).toStrictEqual({ n: 2 }); 36 | }); 37 | 38 | it('handles `_mean` JMESPath function extension', () => { 39 | const array = [4, 2, 8, 6]; 40 | expect(search(array, '_mean(@)')).toStrictEqual(5); 41 | }); 42 | 43 | it('handles `_meanBy` JMESPath function extension', () => { 44 | const objects = [{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }]; 45 | expect(search(objects, "_meanBy(@, as_lambda('o => o.n'))")).toStrictEqual(5); 46 | expect(search(objects, "_meanBy(@, 'n')")).toStrictEqual(5); 47 | }); 48 | 49 | it('handles `_min` JMESPath function extension', () => { 50 | const array = [4, 2, 8, 6]; 51 | expect(search(array, '_min(@)')).toStrictEqual(2); 52 | expect(search([], '_min(@)')).toStrictEqual(undefined); 53 | }); 54 | 55 | it('handles `_minBy` JMESPath function extension', () => { 56 | const objects = [{ n: 1 }, { n: 2 }]; 57 | expect(search(objects, "_minBy(@, as_lambda('o => o.n'))")).toStrictEqual({ n: 1 }); 58 | expect(search(objects, "_minBy(@, 'n')")).toStrictEqual({ n: 1 }); 59 | }); 60 | 61 | it('handles `_multiply` JMESPath function extension', () => { 62 | const array = [6, 4]; 63 | expect(search(array, '_multiply([0], [1])')).toStrictEqual(24); 64 | }); 65 | 66 | it('handles `_round` JMESPath function extension', () => { 67 | expect(search(4.006, '_round(@)')).toStrictEqual(4); 68 | expect(search(4.006, '_round(@, `2`)')).toStrictEqual(4.01); 69 | expect(search(4060, '_round(@, `-2`)')).toStrictEqual(4100); 70 | }); 71 | 72 | it('handles `_subtract` JMESPath function extension', () => { 73 | const array = [6, 4]; 74 | expect(search(array, '_subtract([0], [1])')).toStrictEqual(2); 75 | }); 76 | 77 | it('handles `_sum` JMESPath function extension', () => { 78 | const array = [4, 2, 8, 6]; 79 | expect(search(array, '_sum(@)')).toStrictEqual(20); 80 | }); 81 | 82 | it('handles `_sumBy` JMESPath function extension', () => { 83 | const objects = [{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }]; 84 | expect(search(objects, "_sumBy(@, as_lambda('o => o.n'))")).toStrictEqual(20); 85 | expect(search(objects, "_sumBy(@, 'n')")).toStrictEqual(20); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/lodash/number.spec.ts: -------------------------------------------------------------------------------- 1 | import { search } from '../../src'; 2 | 3 | describe('LODASH EXTENSIONS (NUMBER)', () => { 4 | it('handles `_clamp` JMESPath function extension', () => { 5 | expect(search(-10, '_clamp(@, `-5`, `5`)')).toStrictEqual(-5); 6 | expect(search(-10, '_clamp(@, `-5`, `5`)')).toStrictEqual(-5); 7 | expect(search(10, '_clamp(@, `5`)')).toStrictEqual(5); 8 | }); 9 | 10 | it('handles `_inRange` JMESPath function extension', () => { 11 | expect(search([3, 2, 4], '_inRange([0], [1], [2])')).toStrictEqual(true); 12 | expect(search([4, 8], '_inRange([0], [1])')).toStrictEqual(true); 13 | expect(search([4, 2], '_inRange([0], [1])')).toStrictEqual(false); 14 | expect(search([2, 2], '_inRange([0], [1])')).toStrictEqual(false); 15 | expect(search([1.2, 2], '_inRange([0], [1])')).toStrictEqual(true); 16 | expect(search([5.2, 4], '_inRange([0], [1])')).toStrictEqual(false); 17 | expect(search([-3, -2, -6], '_inRange([0], [1], [2])')).toStrictEqual(true); 18 | }); 19 | 20 | // it('handles `_random` JMESPath function extension', () => { 21 | // expect(search([0, 5], '_random([0], [1])')).toBeGreaterThanOrEqual(0); 22 | // expect(search([5], '_random([0])')).toStrictEqual(true); 23 | // expect(search([5, true], '_random([0], [1])')).toStrictEqual(false); 24 | // expect(search([1.2, 5.2], '_random([0], [1])')).toStrictEqual(false); 25 | // }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/lodash/object.spec.ts: -------------------------------------------------------------------------------- 1 | import { search } from '../../src'; 2 | 3 | describe('LODASH EXTENSIONS (OBJECT)', () => { 4 | it('handles `_assign` JMESPath function extension', () => { 5 | expect(search({ a: 1 }, "_assign(@, {b: 'c'})")).toStrictEqual({ a: 1, b: 'c' }); 6 | }); 7 | 8 | it('handles `_assignIn` JMESPath function extension', () => { 9 | expect(search({ a: 1 }, "_assignIn(@, {b: 'c'})")).toStrictEqual({ a: 1, b: 'c' }); 10 | }); 11 | 12 | it('handles `_at` JMESPath function extension', () => { 13 | const object = { a: [{ b: { c: 3 } }, 4] }; 14 | expect(search(object, "_at(@, ['a[0].b.c', 'a[1]'])")).toStrictEqual([3, 4]); 15 | }); 16 | 17 | // it('handles `_defaults` JMESPath function extension', () => { 18 | // const array = [{ a: 1 }, { b: 2 }, { a: 3 }]; 19 | // expect(search(array, '_defaults([0], [1], [2])')).toStrictEqual({ a: 1, b: 2 }); 20 | // }); 21 | 22 | // it('handles `_defaultsDeep` JMESPath function extension', () => { 23 | // const array = [{ a: { b: 2 } }, { a: { b: 1, c: 3 } }]; 24 | // expect(search(array, '_defaultsDeep([0], [1])')).toStrictEqual({ a: { b: 2, c: 3 } }); 25 | // }); 26 | 27 | it('handles `_findKey` JMESPath function extension', () => { 28 | const users = { 29 | barney: { age: 36, active: true }, 30 | fred: { age: 40, active: false }, 31 | pebbles: { age: 1, active: true }, 32 | }; 33 | expect(search(users, "_findKey(@, as_lambda('o => o.age < 40'))")).toStrictEqual('barney'); 34 | expect(search(users, '_findKey(@, { age: `1`, active: `true` })')).toStrictEqual('pebbles'); 35 | expect(search(users, "_findKey(@, ['active', `false` ])")).toStrictEqual('fred'); 36 | expect(search(users, "_findKey(@, 'active')")).toStrictEqual('barney'); 37 | }); 38 | 39 | it('handles `_findLastKey` JMESPath function extension', () => { 40 | const users = { 41 | barney: { age: 36, active: true }, 42 | fred: { age: 40, active: false }, 43 | pebbles: { age: 1, active: true }, 44 | }; 45 | expect(search(users, "_findLastKey(@, as_lambda('o => o.age < 40'))")).toStrictEqual('pebbles'); 46 | expect(search(users, '_findLastKey(@, { age: `36`, active: `true` })')).toStrictEqual('barney'); 47 | expect(search(users, "_findLastKey(@, ['active', `false` ])")).toStrictEqual('fred'); 48 | expect(search(users, "_findLastKey(@, 'active')")).toStrictEqual('pebbles'); 49 | }); 50 | 51 | it('handles `_get` JMESPath function extension', () => { 52 | const object = { a: [{ b: { c: 3 } }] }; 53 | expect(search(object, "_get(@, 'a[0].b.c')")).toStrictEqual(3); 54 | expect(search(object, "_get(@, ['a', '0', 'b', 'c'])")).toStrictEqual(3); 55 | expect(search(object, "_get(@, 'a.b.c', 'default')")).toStrictEqual('default'); 56 | }); 57 | 58 | it('handles `_has` JMESPath function extension', () => { 59 | const object = { a: { b: 2 } }; 60 | const other = { b: { a: 2 } }; 61 | expect(search(object, "_has(@, 'a')")).toStrictEqual(true); 62 | expect(search(object, "_has(@, 'a.b')")).toStrictEqual(true); 63 | expect(search(object, "_has(@, ['a', 'b'])")).toStrictEqual(true); 64 | expect(search(other, "_has(@, 'a')")).toStrictEqual(false); 65 | }); 66 | 67 | it('handles `_invert` JMESPath function extension', () => { 68 | const object = { a: 1, b: 2, c: 1 }; 69 | expect(search(object, '_invert(@)')).toStrictEqual({ '1': 'c', '2': 'b' }); 70 | }); 71 | 72 | it('handles `_invertBy` JMESPath function extension', () => { 73 | const object = { a: 1, b: 2, c: 1 }; 74 | expect(search(object, '_invertBy(@)')).toStrictEqual({ '1': ['a', 'c'], '2': ['b'] }); 75 | expect(search(object, "_invertBy(@, as_lambda('v => `group${v}`'))")).toStrictEqual({ 76 | group1: ['a', 'c'], 77 | group2: ['b'], 78 | }); 79 | }); 80 | 81 | it('handles `_invoke` JMESPath function extension', () => { 82 | const object = { a: [{ b: { c: [1, 2, 3, 4] } }] }; 83 | expect(search(object, "_invoke(@, 'a[0].b.c.slice', `1`, `3`)")).toStrictEqual([2, 3]); 84 | }); 85 | 86 | it('handles `_keys` JMESPath function extension', () => { 87 | expect(search({ a: 1, b: 2 }, '_keys(@)')).toStrictEqual(['a', 'b']); 88 | expect(search(['a', 'b'], '_keys(@)')).toStrictEqual(['0', '1']); 89 | expect(search('ab', '_keys(@)')).toStrictEqual(['0', '1']); 90 | }); 91 | 92 | it('handles `_mapKeys` JMESPath function extension', () => { 93 | expect(search({ a: 1, b: 2 }, "_mapKeys(@, as_lambda('(v, k) => k + v'))")).toStrictEqual({ a1: 1, b2: 2 }); 94 | }); 95 | 96 | it('handles `_mapValues` JMESPath function extension', () => { 97 | const users = { 98 | fred: { user: 'fred', age: 40 }, 99 | pebbles: { user: 'pebbles', age: 1 }, 100 | }; 101 | expect(search(users, "_mapValues(@, as_lambda('o => o.age'))")).toStrictEqual({ fred: 40, pebbles: 1 }); 102 | expect(search(users, "_mapValues(@, 'age')")).toStrictEqual({ fred: 40, pebbles: 1 }); 103 | }); 104 | 105 | // it('handles `_merge` JMESPath function extension', () => { 106 | // const object = { 107 | // a: [{ b: 2 }, { d: 4 }], 108 | // }; 109 | 110 | // const other = { 111 | // a: [{ c: 3 }, { e: 5 }], 112 | // }; 113 | // expect(search([object, other], '_merge([0], [1])')).toStrictEqual({ 114 | // a: [ 115 | // { b: 2, c: 3 }, 116 | // { d: 4, e: 5 }, 117 | // ], 118 | // }); 119 | // }); 120 | 121 | it('handles `_omit` JMESPath function extension', () => { 122 | const object = { a: 1, b: '2', c: 3 }; 123 | expect(search(object, "_omit(@, 'a', 'c')")).toStrictEqual({ b: '2' }); 124 | expect(search(object, "_omit(@, ['a'], ['c'])")).toStrictEqual({ b: '2' }); 125 | expect(search(object, "_omit(@, ['a', 'c'])")).toStrictEqual({ b: '2' }); 126 | }); 127 | 128 | it('handles `_omitBy` JMESPath function extension', () => { 129 | const object = { a: 1, b: '2', c: 3 }; 130 | expect(search(object, "_omitBy(@, as_lambda('x => typeof x === `number`'))")).toStrictEqual({ b: '2' }); 131 | }); 132 | 133 | it('handles `_pick` JMESPath function extension', () => { 134 | const object = { a: 1, b: '2', c: 3 }; 135 | expect(search(object, "_pick(@, 'a', 'c')")).toStrictEqual({ a: 1, c: 3 }); 136 | expect(search(object, "_pick(@, ['a'], ['c'])")).toStrictEqual({ a: 1, c: 3 }); 137 | expect(search(object, "_pick(@, ['a', 'c'])")).toStrictEqual({ a: 1, c: 3 }); 138 | }); 139 | 140 | it('handles `_pickBy` JMESPath function extension', () => { 141 | const object = { a: 1, b: '2', c: 3 }; 142 | expect(search(object, "_pickBy(@, as_lambda('x => typeof x === `number`'))")).toStrictEqual({ a: 1, c: 3 }); 143 | }); 144 | 145 | it('handles `_set` JMESPath function extension', () => { 146 | const object = { a: [{ b: { c: 3 } }] }; 147 | search(object, "_set(@, 'a[0].b.c', `4`)"); 148 | expect(object).toStrictEqual({ a: [{ b: { c: 4 } }] }); 149 | search(object, "_set(@, ['x', '0', 'y', 'z'], `5`)"); 150 | expect(object).toStrictEqual({ a: [{ b: { c: 4 } }], x: [{ y: { z: 5 } }] }); 151 | }); 152 | 153 | it('handles `_toPairs` JMESPath function extension', () => { 154 | const object = { a: 1, b: '2', c: 3 }; 155 | expect(search(object, '_toPairs(@)')).toStrictEqual([ 156 | ['a', 1], 157 | ['b', '2'], 158 | ['c', 3], 159 | ]); 160 | }); 161 | 162 | it('handles `_entries` JMESPath function extension', () => { 163 | const object = { a: 1, b: '2', c: 3 }; 164 | expect(search(object, '_entries(@)')).toStrictEqual([ 165 | ['a', 1], 166 | ['b', '2'], 167 | ['c', 3], 168 | ]); 169 | }); 170 | 171 | it('handles `_transform` JMESPath function extension', () => { 172 | expect( 173 | search([2, 3, 4], "_transform(@, as_lambda('(result, n) => {result.push(n*n); return n %2 === 0}'), `[]`)"), 174 | ).toStrictEqual([4, 9]); 175 | expect( 176 | search( 177 | { a: 1, b: 2, c: 1 }, 178 | "_transform(@, as_lambda('(r, v, k) => Object.assign(r, {[v]: [...(r[v] || []), k]})'), `{}`)", 179 | ), 180 | ).toStrictEqual({ '1': ['a', 'c'], '2': ['b'] }); 181 | }); 182 | 183 | it('handles `_unset` JMESPath function extension', () => { 184 | const object = { a: [{ b: { c: 7 } }] }; 185 | expect(search(object, "_unset(@, 'a[0].b.c')")).toStrictEqual(true); 186 | expect(object).toStrictEqual({ a: [{ b: {} }] }); 187 | search(object, "_unset(@, ['a', '0', 'b', 'c'])"); 188 | expect(object).toStrictEqual({ a: [{ b: {} }] }); 189 | }); 190 | 191 | it('handles `_update` JMESPath function extension', () => { 192 | const object = { a: [{ b: { c: 3 } }] }; 193 | search(object, "_update(@, 'a[0].b.c', as_lambda('n => n * n'))"); 194 | expect(object).toStrictEqual({ a: [{ b: { c: 9 } }] }); 195 | search(object, "_update(@, ['a', '0', 'b', 'c'], as_lambda('n => n * n'))"); 196 | expect(object).toStrictEqual({ a: [{ b: { c: 81 } }] }); 197 | search(object, "_update(@, 'x[0].y.z', as_lambda('n => n ? n + 1 : 0') )"); 198 | expect(object).toStrictEqual({ a: [{ b: { c: 81 } }], x: [{ y: { z: 0 } }] }); 199 | }); 200 | 201 | it('handles `_values` JMESPath function extension', () => { 202 | const object = { a: 1, b: '2', c: 3 }; 203 | expect(search(object, '_values(@)')).toStrictEqual([1, '2', 3]); 204 | expect(search('Hi', '_values(@)')).toStrictEqual(['H', 'i']); 205 | }); 206 | }); 207 | -------------------------------------------------------------------------------- /test/lodash/string.spec.ts: -------------------------------------------------------------------------------- 1 | import { search } from '../../src'; 2 | 3 | describe('LODASH EXTENSIONS (STRING)', () => { 4 | it('handles `_camelCase` JMESPath function extension', () => { 5 | expect(search('Foo Bar', '_camelCase(@)')).toStrictEqual('fooBar'); 6 | expect(search('--foo-bar--', '_camelCase(@)')).toStrictEqual('fooBar'); 7 | expect(search('__FOO_BAR__', '_camelCase(@)')).toStrictEqual('fooBar'); 8 | }); 9 | 10 | it('handles `_capitalize` JMESPath function extension', () => { 11 | expect(search('FRED', '_capitalize(@)')).toStrictEqual('Fred'); 12 | }); 13 | 14 | it('handles `_deburr` JMESPath function extension', () => { 15 | expect(search('déjà vu', '_deburr(@)')).toStrictEqual('deja vu'); 16 | }); 17 | 18 | it('handles `_endsWith` JMESPath function extension', () => { 19 | expect(search('abc', "_endsWith(@, 'c')")).toStrictEqual(true); 20 | expect(search('abc', "_endsWith(@, 'b')")).toStrictEqual(false); 21 | expect(search('abc', "_endsWith(@, 'b', `2`)")).toStrictEqual(true); 22 | }); 23 | 24 | it('handles `_escape` JMESPath function extension', () => { 25 | expect(search('fred, barney, & pebbles', '_escape(@)')).toStrictEqual('fred, barney, & pebbles'); 26 | }); 27 | 28 | it('handles `_escapeRegExp` JMESPath function extension', () => { 29 | expect(search('[lodash](https://lodash.com/)', '_escapeRegExp(@)')).toStrictEqual( 30 | '\\[lodash\\]\\(https://lodash\\.com/\\)', 31 | ); 32 | }); 33 | 34 | it('handles `_kebabCase` JMESPath function extension', () => { 35 | expect(search('Foo Bar', '_kebabCase(@)')).toStrictEqual('foo-bar'); 36 | expect(search('--foo-bar--', '_kebabCase(@)')).toStrictEqual('foo-bar'); 37 | expect(search('__FOO_BAR__', '_kebabCase(@)')).toStrictEqual('foo-bar'); 38 | }); 39 | 40 | it('handles `_lowerCase` JMESPath function extension', () => { 41 | expect(search('--Foo-Bar--', '_lowerCase(@)')).toStrictEqual('foo bar'); 42 | expect(search('fooBar', '_lowerCase(@)')).toStrictEqual('foo bar'); 43 | expect(search('__FOO_BAR__', '_lowerCase(@)')).toStrictEqual('foo bar'); 44 | }); 45 | 46 | it('handles `_lowerFirst` JMESPath function extension', () => { 47 | expect(search('Fred', '_lowerFirst(@)')).toStrictEqual('fred'); 48 | expect(search('FRED', '_lowerFirst(@)')).toStrictEqual('fRED'); 49 | }); 50 | 51 | it('handles `_pad` JMESPath function extension', () => { 52 | expect(search('abc', '_pad(@, `8`)')).toStrictEqual(' abc '); 53 | expect(search('abc', "_pad(@, `8`, '_-')")).toStrictEqual('_-abc_-_'); 54 | expect(search('abc', '_pad(@, `3`)')).toStrictEqual('abc'); 55 | }); 56 | 57 | it('handles `_padEnd` JMESPath function extension', () => { 58 | expect(search('abc', '_padEnd(@, `6`)')).toStrictEqual('abc '); 59 | expect(search('abc', "_padEnd(@, `6`, '_-')")).toStrictEqual('abc_-_'); 60 | expect(search('abc', '_padEnd(@, `3`)')).toStrictEqual('abc'); 61 | }); 62 | 63 | it('handles `_padStart` JMESPath function extension', () => { 64 | expect(search('abc', '_padStart(@, `6`)')).toStrictEqual(' abc'); 65 | expect(search('abc', "_padStart(@, `6`, '_-')")).toStrictEqual('_-_abc'); 66 | expect(search('abc', '_padStart(@, `3`)')).toStrictEqual('abc'); 67 | }); 68 | 69 | it('handles `_parseInt` JMESPath function extension', () => { 70 | expect(search('08', '_parseInt(@)')).toStrictEqual(8); 71 | expect(search('ff', '_parseInt(@, `16`)')).toStrictEqual(255); 72 | }); 73 | 74 | it('handles `_repeat` JMESPath function extension', () => { 75 | expect(search('*', '_repeat(@, `3`)')).toStrictEqual('***'); 76 | expect(search('abc', '_repeat(@, `2`)')).toStrictEqual('abcabc'); 77 | expect(search('abc', '_repeat(@, `0`)')).toStrictEqual(''); 78 | }); 79 | 80 | it('handles `_replace` JMESPath function extension', () => { 81 | expect(search('Hi Fred', "_replace(@, 'Fred', 'Barney')")).toStrictEqual('Hi Barney'); 82 | expect(search('Hi Fred', "_replace(@, as_regexp('[edr]+'), 'lower')")).toStrictEqual('Hi Flower'); 83 | }); 84 | 85 | it('handles `_snakeCase` JMESPath function extension', () => { 86 | expect(search('Foo Bar', '_snakeCase(@)')).toStrictEqual('foo_bar'); 87 | expect(search('fooBar', '_snakeCase(@)')).toStrictEqual('foo_bar'); 88 | expect(search('--FOO-BAR--', '_snakeCase(@)')).toStrictEqual('foo_bar'); 89 | }); 90 | 91 | it('handles `_split` JMESPath function extension', () => { 92 | expect(search('a-b-c', "_split(@, '-', `2`)")).toStrictEqual(['a', 'b']); 93 | }); 94 | 95 | it('handles `_startCase` JMESPath function extension', () => { 96 | expect(search('--foo-bar--', '_startCase(@)')).toStrictEqual('Foo Bar'); 97 | expect(search('fooBar', '_startCase(@)')).toStrictEqual('Foo Bar'); 98 | expect(search('__FOO_BAR__', '_startCase(@)')).toStrictEqual('FOO BAR'); 99 | }); 100 | 101 | it('handles `_startsWith` JMESPath function extension', () => { 102 | expect(search('abc', "_startsWith(@, 'a')")).toStrictEqual(true); 103 | expect(search('abc', "_startsWith(@, 'b')")).toStrictEqual(false); 104 | expect(search('abc', "_startsWith(@, 'b', `1`)")).toStrictEqual(true); 105 | }); 106 | 107 | it('handles `_toLower` JMESPath function extension', () => { 108 | expect(search('--Foo-Bar--', '_toLower(@)')).toStrictEqual('--foo-bar--'); 109 | expect(search('fooBar', '_toLower(@)')).toStrictEqual('foobar'); 110 | expect(search('__FOO_BAR__', '_toLower(@)')).toStrictEqual('__foo_bar__'); 111 | }); 112 | 113 | it('handles `_toUpper` JMESPath function extension', () => { 114 | expect(search('--Foo-Bar--', '_toUpper(@)')).toStrictEqual('--FOO-BAR--'); 115 | expect(search('fooBar', '_toUpper(@)')).toStrictEqual('FOOBAR'); 116 | expect(search('__FOO_BAR__', '_toUpper(@)')).toStrictEqual('__FOO_BAR__'); 117 | }); 118 | 119 | it('handles `_trim` JMESPath function extension', () => { 120 | expect(search(' abc ', '_trim(@)')).toStrictEqual('abc'); 121 | expect(search('-_-abc-_-', "_trim(@, '_-')")).toStrictEqual('abc'); 122 | }); 123 | 124 | it('handles `_trimEnd` JMESPath function extension', () => { 125 | expect(search(' abc ', '_trimEnd(@)')).toStrictEqual(' abc'); 126 | expect(search('-_-abc-_-', "_trimEnd(@, '_-')")).toStrictEqual('-_-abc'); 127 | }); 128 | 129 | it('handles `_trimStart` JMESPath function extension', () => { 130 | expect(search(' abc ', '_trimStart(@)')).toStrictEqual('abc '); 131 | expect(search('-_-abc-_-', "_trimStart(@, '_-')")).toStrictEqual('abc-_-'); 132 | }); 133 | 134 | it('handles `_unescape` JMESPath function extension', () => { 135 | expect(search('fred, barney, & pebbles', '_unescape(@)')).toStrictEqual('fred, barney, & pebbles'); 136 | }); 137 | 138 | it('handles `_upperCase` JMESPath function extension', () => { 139 | expect(search('--foo-bar--', '_upperCase(@)')).toStrictEqual('FOO BAR'); 140 | expect(search('fooBar', '_upperCase(@)')).toStrictEqual('FOO BAR'); 141 | expect(search('__FOO_BAR__', '_upperCase(@)')).toStrictEqual('FOO BAR'); 142 | }); 143 | 144 | it('handles `_upperFirst` JMESPath function extension', () => { 145 | expect(search('fred', '_upperFirst(@)')).toStrictEqual('Fred'); 146 | expect(search('fRED', '_upperFirst(@)')).toStrictEqual('FRED'); 147 | expect(search('FRED', '_upperFirst(@)')).toStrictEqual('FRED'); 148 | }); 149 | 150 | it('handles `_words` JMESPath function extension', () => { 151 | expect(search('fred, barney, & pebbles', '_words(@)')).toStrictEqual(['fred', 'barney', 'pebbles']); 152 | expect(search('fred, barney, & pebbles', "_words(@, as_regexp('[^, ]+', 'g'))")).toStrictEqual([ 153 | 'fred', 154 | 'barney', 155 | '&', 156 | 'pebbles', 157 | ]); 158 | }); 159 | }); 160 | -------------------------------------------------------------------------------- /test/lodash/util.spec.ts: -------------------------------------------------------------------------------- 1 | import { search } from '../../src'; 2 | 3 | describe('LODASH EXTENSIONS (UTIL)', () => { 4 | it('handles `_range` JMESPath function extension', () => { 5 | expect(search(null, '_range(`4`)')).toStrictEqual([0, 1, 2, 3]); 6 | expect(search(null, '_range(`-4`)')).toStrictEqual([0, -1, -2, -3]); 7 | expect(search(null, '_range(`1`, `5`)')).toStrictEqual([1, 2, 3, 4]); 8 | expect(search(null, '_range(`0`, `20`, `5`)')).toStrictEqual([0, 5, 10, 15]); 9 | expect(search(null, '_range(`0`, `-4`, `-1`)')).toStrictEqual([0, -1, -2, -3]); 10 | expect(search(null, '_range(`1`, `4`, `0`)')).toStrictEqual([1, 1, 1]); 11 | expect(search(null, '_range(`0`)')).toStrictEqual([]); 12 | }); 13 | it('handles `_rangeRight` JMESPath function extension', () => { 14 | expect(search(null, '_rangeRight(`4`)')).toStrictEqual([3, 2, 1, 0]); 15 | expect(search(null, '_rangeRight(`-4`)')).toStrictEqual([-3, -2, -1, 0]); 16 | expect(search(null, '_rangeRight(`1`, `5`)')).toStrictEqual([4, 3, 2, 1]); 17 | expect(search(null, '_rangeRight(`0`, `20`, `5`)')).toStrictEqual([15, 10, 5, 0]); 18 | expect(search(null, '_rangeRight(`0`, `-4`, `-1`)')).toStrictEqual([-3, -2, -1, 0]); 19 | expect(search(null, '_rangeRight(`1`, `4`, `0`)')).toStrictEqual([1, 1, 1]); 20 | expect(search(null, '_rangeRight(`0`)')).toStrictEqual([]); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/plugins.spec.ts: -------------------------------------------------------------------------------- 1 | import { search } from '@metrichor/jmespath'; 2 | import { loadPlugins } from '../src/plugins'; 3 | 4 | describe('NATIVE JMESPATH EXTENSIONS', () => { 5 | it('should augment jmespath std lib', () => { 6 | expect(() => search('Foo-bar_fez-baz', 'split(`-`, @)')).toThrow('Unknown function: split()'); 7 | loadPlugins(); 8 | expect(() => search('Foo-bar_fez-baz', 'split(`-`, @)')).not.toThrow(); 9 | expect(search('Foo-bar_fez-baz', 'split(`-`, @)')).toStrictEqual(['Foo', 'bar_fez', 'baz']); 10 | }); 11 | 12 | it('applies the `mode` function', () => { 13 | // Calculate the most common value 14 | 15 | let returnValue = search([-1, 2.21, -3.2, 4.2, 5.2, 2.21, 3.3, 4.11, 1.33, 5.1, 2.21, 3.2, 1.11, 5.67], 'mode(@)'); 16 | expect(returnValue).toStrictEqual([2.21]); 17 | returnValue = search([], 'mode(@)'); 18 | expect(returnValue).toStrictEqual(null); 19 | returnValue = search([1, 2, 3], 'mode(@)'); 20 | expect(returnValue).toStrictEqual(null); 21 | returnValue = search([8, 9, 10, 10, 10, 11, 11, 11, 12, 13], 'mode(@)'); 22 | expect(returnValue).toStrictEqual([10, 11]); 23 | }); 24 | 25 | it('applies the `as_lambda` function', () => { 26 | // Calculate the most common value 27 | 28 | const returnValue = search( 29 | [-1, 2.21, -3.2, 4.2, 5.2, 2.21, 3.3, 4.11, 1.33, 5.1, 2.21, 3.2, 1.11, 5.67], 30 | "_flatMap(@, as_lambda('x => Math.floor(x)'))", 31 | ); 32 | expect(returnValue).toStrictEqual([-1, 2, -4, 4, 5, 2, 3, 4, 1, 5, 2, 3, 1, 5]); 33 | }); 34 | 35 | it('applies the `as_regexp` function', () => { 36 | let regexp = search('abc', 'as_regexp(@)') as unknown; 37 | expect(regexp).toBeInstanceOf(RegExp); 38 | expect((regexp as RegExp).test('abcde')).toStrictEqual(true); 39 | expect((regexp as RegExp).test('ABCde')).toStrictEqual(false); 40 | regexp = search('abc', "as_regexp(@, 'i')"); 41 | expect((regexp as RegExp).test('ABCde')).toStrictEqual(true); 42 | 43 | expect(() => search('(\\{{]+?}}', 'as_regexp(@)')).toThrow( 44 | 'Invalid regular expression: /(\\{{]+?}}/: Unterminated group', 45 | ); 46 | }); 47 | 48 | it('applies the `median` function', () => { 49 | // Calculate the most common value 50 | let returnValue = search( 51 | [-1, 2.21, -3.2, 4.2, 5.2, 2.21, 3.3, 4.11, 1.33, 5.1, 2.21, 3.2, 1.11, 5.67], 52 | 'median(@)', 53 | ); 54 | expect(returnValue).toStrictEqual(2.705); 55 | returnValue = search([], 'median(@)'); 56 | expect(returnValue).toStrictEqual(null); 57 | returnValue = search([2, 1, 3], 'median(@)'); 58 | expect(returnValue).toStrictEqual(2); 59 | returnValue = search([4, 2, 1, 3], 'median(@)'); 60 | expect(returnValue).toStrictEqual(2.5); 61 | returnValue = search([8, 9, 10, 10, 10, 11, 11, 11, 12, 13], 'median(@)'); 62 | expect(returnValue).toStrictEqual(10.5); 63 | }); 64 | 65 | it('applies the `mod` function', () => { 66 | // Calculate the modulus of two numbers 67 | let returnValue = search([27, 2], 'mod([0], [1])'); 68 | expect(returnValue).toStrictEqual(1); 69 | returnValue = search([26, 2], 'mod([0], [1])'); 70 | expect(returnValue).toStrictEqual(0); 71 | }); 72 | 73 | it('applies the `divide` function', () => { 74 | // Calculate the division of two numbers 75 | let returnValue = search([27, 2], 'divide([0], [1])'); 76 | expect(returnValue).toStrictEqual(13.5); 77 | returnValue = search([26, 2], 'divide([0], [1])'); 78 | expect(returnValue).toStrictEqual(13); 79 | }); 80 | 81 | it('applies the `split` function', () => { 82 | // Calculate the division of two numbers 83 | const returnValue = search('Foo-bar_fez-baz', 'split(`-`, @)'); 84 | expect(returnValue).toStrictEqual(['Foo', 'bar_fez', 'baz']); 85 | }); 86 | 87 | it('applies the `format` function', () => { 88 | // Calculate the division of two numbers 89 | let returnValue = search(['Mr', 'zebra'], 'format(`Hello ${100} ${animal}`, @)'); 90 | expect(returnValue).toStrictEqual('Hello ${100} ${animal}'); 91 | returnValue = search(['Mr', 'zebra'], 'format(`Hello ${0} ${1}`, @)'); 92 | expect(returnValue).toStrictEqual('Hello Mr zebra'); 93 | returnValue = search({ title: 'Mr', animal: 'zebra' }, 'format(`Hello ${title} ${animal}`, @)'); 94 | expect(returnValue).toStrictEqual('Hello Mr zebra'); 95 | returnValue = search({ title: null, animal: 'zebra' }, 'format(`Hello ${title} ${animal}`, @)'); 96 | expect(returnValue).toStrictEqual('Hello zebra'); 97 | }); 98 | 99 | it('applies the `entries` function', () => { 100 | // Calculate the division of two numbers 101 | const returnValue = search( 102 | { 103 | category_1: [{ count: 10, name: 'medium' }], 104 | category_2: [{ count: 40, name: 'high' }], 105 | }, 106 | 'entries(@)', 107 | ); 108 | expect(returnValue).toStrictEqual([ 109 | ['category_1', [{ count: 10, name: 'medium' }]], 110 | ['category_2', [{ count: 40, name: 'high' }]], 111 | ]); 112 | }); 113 | 114 | it('applies the `mean` function', () => { 115 | // Load when there's nothing but ID given 116 | const returnValue = search([13, 18, 13, 14, 13, 16, 14, 21, 13], 'mean(@)'); 117 | expect(returnValue).toStrictEqual(15); 118 | }); 119 | 120 | it('applies the `uniq` function', () => { 121 | // Calculate the most common value 122 | let returnValue = search([1, 2, 4, 3, 2, 3, 4, 1, 2, 3, 2, 3, 3, 4, 2, 1], 'uniq(@)'); 123 | expect(returnValue).toStrictEqual([1, 2, 4, 3]); 124 | 125 | returnValue = search( 126 | [ 127 | 'label-2', 128 | 'label-4', 129 | 'label-3', 130 | 'label-2', 131 | 'label-3', 132 | 'label-4', 133 | 'label-1', 134 | 'label-2', 135 | 'label-3', 136 | 'label-2', 137 | 'label-3', 138 | 'label-3', 139 | 'label-4', 140 | 'label-2', 141 | 'label-1', 142 | ], 143 | 'uniq(@)', 144 | ); 145 | expect(returnValue).toStrictEqual(['label-2', 'label-4', 'label-3', 'label-1']); 146 | }); 147 | 148 | it('applies the `formatNumber` function', () => { 149 | // Load when there's nothing but ID given 150 | let returnValue = search(123456789.123456789, 'formatNumber(@, `2`, `base`)'); 151 | expect(returnValue).toStrictEqual('123.46 Mbases'); 152 | returnValue = search(123456789.123456789, 'formatNumber(@, `1`, `base`)'); 153 | expect(returnValue).toStrictEqual('123.5 Mbases'); 154 | returnValue = search(123456789.123456789, 'formatNumber(@, `1`, ``)'); 155 | expect(returnValue).toStrictEqual('123.5 M'); 156 | returnValue = search(123.45, 'formatNumber(@, `1`, ``)'); 157 | expect(returnValue).toStrictEqual('123.5'); 158 | returnValue = search(123.45, 'formatNumber(@, `1`, `base`)'); 159 | expect(returnValue).toStrictEqual('123.5 bases'); 160 | returnValue = search(123456789.123456789, 'formatNumber(@, `0`, `base`)'); 161 | expect(returnValue).toStrictEqual('124 Mbases'); 162 | returnValue = search(0, 'formatNumber(@, `2`, ``)'); 163 | expect(returnValue).toStrictEqual('0'); 164 | returnValue = search(NaN, 'formatNumber(@, `2`, ``)'); 165 | expect(returnValue).toStrictEqual('0'); 166 | returnValue = search(0, 'formatNumber(@, `2`, `foo`)'); 167 | expect(returnValue).toStrictEqual('0 foos'); 168 | returnValue = search(NaN, 'formatNumber(@, `2`, `foo`)'); 169 | expect(returnValue).toStrictEqual('0 foos'); 170 | 171 | // Test singular 172 | returnValue = search(1000.1, 'formatNumber(@, `1`, `base`)'); 173 | expect(returnValue).toStrictEqual('1.1 kbases'); 174 | returnValue = search(1000.1, 'formatNumber(@, `0`, `base`)'); 175 | expect(returnValue).toStrictEqual('2 kbases'); 176 | returnValue = search(1000.0, 'formatNumber(@, `0`, `base`)'); 177 | expect(returnValue).toStrictEqual('1 kbase'); 178 | returnValue = search(1001, 'formatNumber(@, `1`, `base`)'); 179 | expect(returnValue).toStrictEqual('1.1 kbases'); 180 | returnValue = search(1e6, 'formatNumber(@, `0`, `base`)'); 181 | expect(returnValue).toStrictEqual('1 Mbase'); 182 | }); 183 | 184 | it('applies the `toFixed` function', () => { 185 | // Load when there's nothing but ID given 186 | let returnValue = search(123.45678, 'toFixed(@, `2`)'); 187 | expect(returnValue).toStrictEqual('123.46'); 188 | returnValue = search(0, 'toFixed(@, `2`)'); 189 | expect(returnValue).toStrictEqual('0.00'); 190 | }); 191 | 192 | it('handles `flatMapValues` JMESPath function extension', () => { 193 | // Calculate the most common value 194 | let returnValue = search({ a: [1, 3, 5], b: [2, 4, 6] }, 'flatMapValues(@)'); 195 | expect(returnValue).toStrictEqual([ 196 | ['a', 1], 197 | ['a', 3], 198 | ['a', 5], 199 | ['b', 2], 200 | ['b', 4], 201 | ['b', 6], 202 | ]); 203 | 204 | returnValue = search( 205 | { 206 | a: [true, { x: 3 }, null, 1234, ['XXX']], 207 | b: { x: 2 }, 208 | }, 209 | 'flatMapValues(@)', 210 | ); 211 | expect(returnValue).toStrictEqual([ 212 | ['a', true], 213 | ['a', { x: 3 }], 214 | ['a', null], 215 | ['a', 1234], 216 | ['a', ['XXX']], 217 | ['b', { x: 2 }], 218 | ]); 219 | 220 | returnValue = search( 221 | [ 222 | [1, 3, 5], 223 | [2, 4, 6], 224 | ], 225 | 'flatMapValues(@)', 226 | ); 227 | expect(returnValue).toStrictEqual([ 228 | ['0', 1], 229 | ['0', 3], 230 | ['0', 5], 231 | ['1', 2], 232 | ['1', 4], 233 | ['1', 6], 234 | ]); 235 | }); 236 | 237 | it('handles `toUpperCase` JMESPath function extension', () => { 238 | // Calculate the most common value 239 | const returnValue = search(`Foo bar`, 'toUpperCase(@)'); 240 | expect(returnValue).toStrictEqual('FOO BAR'); 241 | }); 242 | 243 | it('handles `toLowerCase` JMESPath function extension', () => { 244 | // Calculate the most common value 245 | const returnValue = search(`Foo bar`, 'toLowerCase(@)'); 246 | expect(returnValue).toStrictEqual('foo bar'); 247 | }); 248 | 249 | it('handles `trim` JMESPath function extension', () => { 250 | // Calculate the most common value 251 | const returnValue = search(`\n Foo bar \r`, 'trim(@)'); 252 | expect(returnValue).toStrictEqual('Foo bar'); 253 | }); 254 | 255 | it('handles `groupBy` JMESPath function extension', () => { 256 | let returnValue = search( 257 | [ 258 | { a: 1, b: 2 }, 259 | { a: 1, b: 3 }, 260 | { a: 2, b: 2 }, 261 | { a: null, b: 999 }, 262 | ], 263 | 'groupBy(@, `a`)', 264 | ); 265 | expect(returnValue).toStrictEqual({ 266 | 1: [ 267 | { a: 1, b: 2 }, 268 | { a: 1, b: 3 }, 269 | ], 270 | 2: [{ a: 2, b: 2 }], 271 | null: [{ a: null, b: 999 }], 272 | }); 273 | 274 | returnValue = search( 275 | [ 276 | { a: 1, b: 2 }, 277 | { a: 1, b: 3 }, 278 | { a: 2, b: 2 }, 279 | { a: null, b: 999 }, 280 | ], 281 | 'groupBy(@, &a)', 282 | ); 283 | expect(returnValue).toStrictEqual({ 284 | 1: [ 285 | { a: 1, b: 2 }, 286 | { a: 1, b: 3 }, 287 | ], 288 | 2: [{ a: 2, b: 2 }], 289 | null: [{ a: null, b: 999 }], 290 | }); 291 | 292 | returnValue = search([{ a: 1, b: 2 }, { a: 1, b: 3 }, { b: 4 }, { a: null, b: 999 }], 'groupBy(@, &a)'); 293 | expect(returnValue).toStrictEqual({ 294 | 1: [ 295 | { a: 1, b: 2 }, 296 | { a: 1, b: 3 }, 297 | ], 298 | null: [{ b: 4 }, { a: null, b: 999 }], 299 | }); 300 | 301 | try { 302 | returnValue = search([{ a: 1, b: 2 }, `{ a: 1, b: 3 }`, { b: 4 }, 1234], 'groupBy(@, &a)'); 303 | } catch (error) { 304 | expect(error.message).toEqual( 305 | 'TypeError: unexpected type. Expected Array but received Array', 306 | ); 307 | } 308 | }); 309 | 310 | it('handles `combine` JMESPath function extension', () => { 311 | // Calculate the most common value 312 | const returnValue = search( 313 | [ 314 | { 315 | category_1: [ 316 | { 317 | count: 10, 318 | name: 'medium', 319 | }, 320 | ], 321 | }, 322 | { 323 | category_2: [ 324 | { 325 | count: 40, 326 | name: 'high', 327 | }, 328 | ], 329 | }, 330 | ], 331 | 'combine(@)', 332 | ); 333 | expect(returnValue).toStrictEqual({ 334 | category_1: [{ count: 10, name: 'medium' }], 335 | category_2: [{ count: 40, name: 'high' }], 336 | }); 337 | }); 338 | 339 | it('handles `toJSON` JMESPath function extension', () => { 340 | expect( 341 | search( 342 | { 343 | foo: 6, 344 | bar: 7, 345 | }, 346 | 'toJSON([foo, bar], `null`, `2`)', 347 | ), 348 | ).toEqual(`[\n 6,\n 7\n]`); 349 | expect( 350 | search( 351 | { 352 | foo: 6, 353 | bar: 7, 354 | }, 355 | 'toJSON([foo, bar])', 356 | ), 357 | ).toEqual('[6,7]'); 358 | expect( 359 | search( 360 | { 361 | foo: { 362 | bar: 1, 363 | }, 364 | baz: { 365 | bar: 10, 366 | }, 367 | }, 368 | 'toJSON(@, [`baz`, `bar`], `\\t`)', 369 | ), 370 | ).toEqual(`{\n\t"baz": {\n\t\t"bar": 10\n\t}\n}`); 371 | }); 372 | 373 | it('handles `fromJSON` JMESPath function extension', () => { 374 | expect(search('{"foo": "bar"}', 'fromJSON(@)')).toEqual({ foo: 'bar' }); 375 | expect(search('true', 'fromJSON(@)')).toEqual(true); 376 | expect(search('"foo"', 'fromJSON(@)')).toEqual('foo'); 377 | expect(search('[1, 5, "false"]', 'fromJSON(@)')).toEqual([1, 5, 'false']); 378 | expect(search('null', 'fromJSON(@)')).toEqual(null); 379 | }); 380 | }); 381 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "allowUnreachableCode": false, 6 | "declaration": true, 7 | "declarationDir": "dist/types", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "lib": [ 11 | "es2017", 12 | "dom" 13 | ], 14 | "moduleResolution": "node", 15 | "module": "esnext", 16 | "isolatedModules": true, 17 | "esModuleInterop": true, 18 | "target": "es6", 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noImplicitAny": true, 22 | "noImplicitReturns": true, 23 | "noImplicitThis": true, 24 | "resolveJsonModule": true, 25 | "skipLibCheck": true, 26 | "sourceMap": true, 27 | "strict": true, 28 | "strictNullChecks": true, 29 | "suppressImplicitAnyIndexErrors": true, 30 | "types": [ 31 | "lodash", 32 | "jest" 33 | ] 34 | }, 35 | "exclude": [ 36 | "node_modules", 37 | "dist", 38 | "test" 39 | ], 40 | "include": [ 41 | "src" 42 | ] 43 | } 44 | --------------------------------------------------------------------------------