├── LICENSE ├── tests └── Tests.gs ├── README.md └── IMPORTJSONAPI.gs /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 qeet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/Tests.gs: -------------------------------------------------------------------------------- 1 | var data1 = { 2 | "store": { 3 | "book": [ 4 | { 5 | "category": "reference", 6 | "author": "Nigel Rees", 7 | "title": "Sayings of the Century", 8 | "price": 8.95 9 | }, 10 | { 11 | "category": "fiction", 12 | "author": "Evelyn Waugh", 13 | "title": "Sword of Honour", 14 | "price": 12.99 15 | }, 16 | { 17 | "category": "fiction", 18 | "author": "Herman Melville", 19 | "title": "Moby Dick", 20 | "isbn": "0-553-21311-3", 21 | "price": 8.99 22 | }, 23 | { 24 | "category": "fiction", 25 | "author": "J. R. R. Tolkien", 26 | "title": "The Lord of the Rings", 27 | "isbn": "0-395-19395-8", 28 | "price": 22.99 29 | } 30 | ], 31 | "bicycle": { 32 | "color": "red", 33 | "price": 19.95 34 | } 35 | } 36 | } 37 | 38 | var data2 = { 39 | "store" : { 40 | "fruit, bread & wine" : [ 41 | { 42 | "name" : "Banana", 43 | "price" : 1.49 44 | } 45 | ] 46 | } 47 | } 48 | 49 | var data3 = { 50 | "stores" : { 51 | "Borders" : [ 52 | { 53 | "Title" : "Yellow Rivers", 54 | "Author" : "I.P. Daily", 55 | "Price" : 3.99 56 | }, 57 | { 58 | "Title" : "Full Moon", 59 | "Author" : "Seymour Buns", 60 | "Price" : 6.49 61 | } 62 | ], 63 | "Waterstones" : [ 64 | { 65 | "Title" : "Hot Dog", 66 | "Author" : "Frank Furter", 67 | "Price" : 8.50 68 | } 69 | ] 70 | } 71 | } 72 | 73 | function compare_tables(expected, actual) { 74 | if (expected.length > actual.length) { 75 | return "Got less rows than expected" 76 | } else if (expected.length < actual.length) { 77 | return "Got more rows than expected" 78 | } 79 | for (var i=0; i act_row.length) { 83 | return "Got less cols than expected" 84 | } else if (exp_row.length < act_row.length) { 85 | return "Got more cols than expected" 86 | } 87 | for (var j=0; j5)]", "Title") 220 | var actual = [ [ 'Full Moon' ], [ 'Hot Dog' ] ] 221 | check(compare_tables(expected, actual)); 222 | } 223 | 224 | function tests6() { 225 | var expected = IMPORTJSONAPI([], "$", "amount") 226 | var actual = [ [ null ] ] 227 | check(compare_tables(expected, actual)); 228 | 229 | var expected = IMPORTJSONAPI({}, "$.nothing", "amount") 230 | check(!(expected === null)) 231 | 232 | var expected = IMPORTJSONAPI(['a', null, 'b'], "$", "@") 233 | var actual = [ ["a,null,b"] ] 234 | check(compare_tables(expected, actual)); 235 | 236 | var expected = IMPORTJSONAPI([], "$", "@") 237 | var actual = [[""]] 238 | check(compare_tables(expected, actual)); 239 | } 240 | 241 | function test_a() { 242 | var data = IMPORTJSONAPI(data1, "city, isNBAFranchise") 243 | 244 | var data = IMPORTJSONAPI("http://data.nba.net/10s/prod/v1/2018/teams.json", "$.league.*[*]", "city, isNBAFranchise") 245 | Logger.log(data) 246 | } 247 | 248 | function test_b() { 249 | var data = IMPORTJSONAPI("https://financialmodelingprep.com/api/v3/company/historical-discounted-cash-flow/AAPL?period=quarter", "$.historicalDCF[?(@.date === '2014-09-27')]", "date, DCF, 'Stock Price'") 250 | Logger.log(data) 251 | } 252 | 253 | function test_c() { 254 | var data = IMPORTJSONAPI("https://api.graph.cool/simple/v1/swapi", "$..films[*]", "^^name, director", "method=post", "contentType=application/json", "payload={ 'query': '{ allPersons { name films { director } } }' }") 255 | Logger.log(data) 256 | } 257 | 258 | function test_d() { 259 | var data = IMPORTJSONAPI("http://countries.trevorblades.com/", "$.data.countries[*]", "name, languages[0].native", "method=post", "contentType=application/json", "payload={ 'query' : '{ countries { emoji name languages { name native } } }' }") 260 | Logger.log(data) 261 | } 262 | 263 | function test_e() { 264 | var data = IMPORTJSONAPI("http://aviation-edge.com/v2/public/timetable?key=33e556-819cf8&iataCode=den&type=departure", "$..codeshared", "airline.name") 265 | Logger.log(data) 266 | } 267 | 268 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IMPORTJSONAPI 2 | 3 | Provides a custom function to selectively extract data from a JSON or GraphQL API in a tabular format suitable for import into a Google Sheets spreadsheet. 4 | 5 | ## Changelog 6 | - v1.0.5 - Added support for importing data from within a speadsheet (10 July 2021) 7 | - v1.0.4 - Added support for converting values to dates (30 March 2021) 8 | - v1.0.3 - Added support for converting values to numbers (23 November 2020) 9 | - v1.0.2 - Return null instead of empty string for blank columns (3 March 2020) 10 | - v1.0.1 - Fix returning empty results (2 March 2020) 11 | - v1.0.0 - Initial release (23 February 2020) 12 | 13 | ## Installation 14 | 15 | To add this custom function to your spreadsheet, follow this procedure: 16 | 17 | 1. Open the spreadsheet in your browser. 18 | 2. Select the `Tools > Script editor` menu option. This will open a script editor window. You will need to copy and paste the function code into a blank script file. 19 | 4. Copy the entire contents of the IMPORTJSONAPI.gs file. The raw file can found [here](https://raw.githubusercontent.com/qeet/IMPORTJSONAPI/master/IMPORTJSONAPI.gs). 20 | 5. Paste this into the blank Code.gs script file or another blank script file that you have created. 21 | 6. Now save the script by clicking the `Save project` button. 22 | 7. You should now be able to use the `=IMPORTJSONAPI()` function in your sheet. 23 | 24 | ## Usage 25 | 26 | =IMPORTJSONAPI(URL, JSONPath Query, Columns [,Parameter] [,Parameter]) 27 | 28 | ### Examples 29 | The following examples are based on this JSON data: 30 | 31 | ```json 32 | { 33 | "stores" : { 34 | "Borders" : [ 35 | { 36 | "Title" : "Yellow Rivers", 37 | "Author" : "I.P. Daily", 38 | "Price" : 3.99 39 | }, 40 | { 41 | "Title" : "Full Moon", 42 | "Author" : "Seymour Buns", 43 | "Price" : 6.49 44 | } 45 | ], 46 | "Waterstones" : [ 47 | { 48 | "Title" : "Hot Dog", 49 | "Author" : "Frank Furter", 50 | "Price" : 8.50 51 | } 52 | ] 53 | } 54 | } 55 | ``` 56 | 57 | **Get titles of all books** 58 | 59 | =IMPORTJSONAPI("https://test.com/api", "$..Title", "@") 60 | 61 | | Title | 62 | |---------------| 63 | | Yellow Rivers | 64 | | Full Moon | 65 | | Hot Dog | 66 | 67 | **Get all books and authors** 68 | 69 | =IMPORTJSONAPI("https://test.com/api", "$.stores.*[*]", "Title, Author") 70 | 71 | | Title | Author | 72 | |---------------|--------------| 73 | | Yellow Rivers | I.P. Daily | 74 | | Full Moon | Seymour Buns | 75 | | Hot Dog | Frank Furter | 76 | 77 | **Select all books in all stores** 78 | 79 | =IMPORTJSONAPI("https://test.com/api", "$.stores.*[*]", "^.~, Title") 80 | 81 | | Store Name | Title | 82 | |-------------|---------------| 83 | | Borders | Yellow Rivers | 84 | | Borders | Full Moon | 85 | | Waterstones | Hot Dog | 86 | 87 | **The titles of all books with a price greater than 5** 88 | 89 | = IMPORTJSONAPI("https://test.com/api", "$..[?(@.Price>5)]", "Title") 90 | 91 | | Title | 92 | |---------------| 93 | | Full Moon | 94 | | Hot Dog | 95 | 96 | ## Function Arguments 97 | | Parameter | Description | 98 | |--------------------|-----------------------------------------------------------------------------------| 99 | | **URL** | The URL endpoint of the API. | 100 | | **JSONPath Query** | JSONPath query expression. | 101 | | **Columns** | Comma separated list of column path expressions. | 102 | | **Parameter** | Optional list of parameters. | 103 | 104 | ### URL 105 | The URL of the API endpoint. Any query parameters containing characters such as '&' or '=' should be urlencoded. For example: 106 | 107 | =IMPORTJSONAPI("https://api.test.com/store?api_token=ds45%3F6hjkd%3Ddjs, ...) 108 | 109 | You can also import JSON data that is contained within a cell of your spreadsheet by replacing the URL with a reference to the cell: 110 | 111 | =IMPORTJSONAPI(A3, ...) 112 | 113 | ### JSONPath Query 114 | The JSONPath expression to select the data that you wish to extract. Each JSON object matched by the expression will become a row in your spreadsheet. An introduction to JSONPath expressions can be found at . 115 | 116 | The actual JSONPath query implementation used is [JSONPath Plus](https://github.com/s3u/JSONPath) which contains some additional functionality and [examples](https://github.com/s3u/JSONPath#syntax-through-examples). 117 | 118 | ### Columns 119 | 120 | The Columns parameter is a comma separated list of path expressions. Path expressions contain one or more of the following components optionally separated by a period. 121 | 122 | | Component | Description | 123 | |---------------|-----------------------------------------------------------------------------------| 124 | | **keyname** | Specifies the key to a value. Must be quoted if it contains characters other than letters, numbers or the underscore character. if the name contains a comma ',' then it must always be escaped by using %2C instead. | 125 | | **[index]** | Access an array with the specified index. | 126 | | **@** | The current value. | 127 | | **^** | The parent of the current value. | 128 | | **~** | The key name of the current value. This must always appear last in the path. | 129 | | **$** | The root of the JSON object. This must always appear first in the path. | 130 | 131 | If the value returned from the path expression is an array of scalars then the result is a list of the array items delimited by a comma. 132 | 133 | If the value returned from the path expression is an object or an array which does not contain only scalars the result is the first 50 characters of the objects string representation. 134 | 135 | ### Column path expression examples 136 | 137 | All examples are based on the following JSON Object: 138 | 139 | ```json 140 | { 141 | "book" : { 142 | "title": "It", 143 | "author": "S. King", 144 | "orders" : [28, 72] 145 | } 146 | } 147 | ``` 148 | The `Value` column is the result of the JSONPath expression and the `Result` column is the result after the column path expressions have been applied to the value. 149 | 150 | | JSONPath | Value | Columns | Result | 151 | |---------------|-------------------------------------------------------------|---------------|----------------| 152 | | $.book | { "title": "It", "author": "S. King", "orders" : [28, 72] } | title, author | "It", "S.King" | 153 | | $.book.title | "It" | @ | "It" | 154 | | $.book.orders | [28, 72] | @, [1] | "28, 72", "72" | 155 | | $.book.orders | [28, 72] | ^.author | "S.King" | 156 | | $.book | { "title": "It", "author": "S. King", "orders" : [28, 72] } | ~ | "book" | 157 | | $.book.orders | [28, 72] | ^~, [0] | "book", "28" | 158 | | $.book.title | "It" | $.book.author | "S. King" | 159 | 160 | ### Converting the column data type 161 | 162 | You can convert a column that returns a string to a numeric type by appending '>n' to the column path: 163 | 164 | Title, Price>n 165 | 166 | You can convert a column that returns a string to a date type by appending '>d' to the column path: 167 | 168 | Title, PubDate>d 169 | 170 | If you need support for other type conversions then please create a new issue. 171 | 172 | ### Parameters 173 | After the three mandatory function arguments you can specify a variable number of function parameters. Each parameter is of the format: 174 | 175 | "parametername = value" 176 | 177 | If the value contains an equals (=) character then it needs to be replaced with '%3D'. If the value for a JSON parameter (headers or payload) contains a double quote (") then it needs to be replaced with '\\%22'. The value does not need to be quoted even if it is a string. 178 | 179 | | Parameter name | Type | Description | 180 | |----------------|--------|----------------------------------------------------------------------------------------| 181 | | method | String | The HTTP method for the request: get, delete, patch, post, or put. The default is get. | 182 | | headers | Object | A JSON key/value map of HTTP headers for the request. | 183 | | contentType | String | The content type for post requests. The default is 'application/x-www-form-urlencoded' | 184 | | payload | Object | The payload for post requests. | 185 | 186 | ### Parameter Examples 187 | 188 | A basic post request with no payload: 189 | 190 | =IMPORTJSONAPI("https://test.com/api", "$..Title", "@", "method=post") 191 | 192 | A post request with a payload: 193 | 194 | =IMPORTJSONAPI("https://test.com/api", "$..Title", "@", "method=post", "payload={ 'user' : 'andy', 'pass' : 'pandy' }") 195 | 196 | A request with Basic Authorizaton: 197 | 198 | =IMPORTJSONAPI("https://test.com/api", "$..Title", "@", "headers={ 'Authorization' : 'Basic QWxhZGRpbjpPcGVuU2VzYW1l' }") 199 | 200 | ## GraphQL 201 | 202 | To query a GraphQL API endpoint you need to set the `method`, `contentType` and `payload` parameters. 203 | 204 | | Parameter | Value | 205 | |-------------|-----------------------------------| 206 | | method | post | 207 | | contentType | application/json | 208 | | payload | { 'query': 'YOUR_GRAPHQL_QUERY' } | 209 | 210 | **Example** 211 | 212 | = IMPORTJSONAPI("https://api.graph.cool/simple/v1/swapi", "$..films[*]", "^^name, director", "method=post", "contentType=application/json", "payload={ 'query': '{ allPersons { name films { director } } }' }") 213 | 214 | ## Refreshing Data 215 | By default Google Sheets only refreshes the results of a custom function every hour or so. If you want to force a refresh then this can be achieved by changing any of the function arguments. The easiest way of doing this is to add a 'dummy parameter' to the end of the function arguments. The dummy parameter should either be a number or a boolean and will be ignored by the import function. 216 | 217 | **Example** 218 | 219 | =IMPORTJSONAPI("https://test.com/api", "$..Title", "@", 1) 220 | 221 | You can now force a refresh by incrementing the number at the end of the function. 222 | 223 | ## Debugging 224 | When you are trying to create the JSONPath query to filter your data, it is sometimes difficult to tell if you are getting the correct results. To help with this you should set the columns parameter to a single '@'. This will then output the list of objects that is being returned by the query. Once you are happy with the results you can then modify the columns to extract the relevant fields. 225 | 226 | **Example** 227 | 228 | =IMPORTJSONAPI("https://test.com/api", "$..book[?(@parent.bicycle && @parent.bicycle.color === "red")].category", "@") 229 | -------------------------------------------------------------------------------- /IMPORTJSONAPI.gs: -------------------------------------------------------------------------------- 1 | /*====================================================================================================================================* 2 | IMPORTJSONAPI 3 | ==================================================================================================================================== 4 | Version: 1.0.5 5 | Project Page: https://github.com/qeet/IMPORTJSONAPI 6 | License: The MIT License (MIT) 7 | ------------------------------------------------------------------------------------------------------------------------------------ 8 | 9 | Changelog: 10 | 1.0.5 Added support for importing data from within a speadsheet (10 July 2021) 11 | 1.0.4 Added support for converting values to dates (30 March 2021) 12 | 1.0.3 Added support for converting values to numbers (23 November 2020) 13 | 1.0.2 Return null instead of empty string for blank columns (3 March 2020) 14 | 1.0.1 Fix returning empty results (2 March 2020) 15 | 1.0.0 Initial release (23 February 2020) 16 | *====================================================================================================================================*/ 17 | 18 | /** 19 | * Return data from a JSON API. 20 | * 21 | * @param {string} url The URL of the API endpoint. 22 | * @param {string} query The JSONPath query expression. 23 | * @param {string} columns A comma separated list of column path expressions. 24 | * @param {string} [param] An optional parameter. 25 | * @return A two-dimensional array containing the data. 26 | * @customfunction 27 | */ 28 | function IMPORTJSONAPI(url, query, cols) { 29 | try { 30 | 31 | if (!(typeof url === "object" || typeof url === "string")) { 32 | throw new Error("Parameter 1 must be a JSON object or URL") 33 | } 34 | 35 | if (typeof query !== "string" || query === "") { 36 | throw new Error("Parameter 2 must be a JSONPath query") 37 | } 38 | 39 | if (typeof cols === "string") { 40 | cols = cols.split(",") 41 | if (cols.length === 1 && cols[0] ==="") { 42 | throw new Error("Parameter 3 must specify at least 1 column") 43 | } 44 | } else { 45 | throw new Error("Parameter 3 must be a list of columns") 46 | } 47 | 48 | var params = {}; 49 | for (var i=3; i") 79 | cols_conv.push(c.length > 1 ? c[1] : "") 80 | cols_code.push(compile_path_(c[0])) 81 | } 82 | var table_data = [] 83 | JSONPath.JSONPath(query, json, function(val, _, details) { 84 | row_data = [] 85 | for (var i = 0; i < cols_code.length; i++) { 86 | var path = JSONPath.JSONPath.toPathArray(details.path).slice() 87 | var data = exec_(cols_code[i], val, path, json) 88 | row_data.push(convert_data_(cols_conv[i], gs_literal_(data))) 89 | } 90 | table_data.push(row_data) 91 | }) 92 | 93 | if (table_data.length == 0) { 94 | table_data = null; 95 | } 96 | 97 | return table_data; 98 | } 99 | 100 | function do_fetch_(url, params) { 101 | if (params['contentType'] === "application/json" && typeof params['payload'] === 'object' ) { 102 | params['payload'] = JSON.stringify(params['payload']) 103 | } 104 | var response = UrlFetchApp.fetch(url, params) 105 | return JSON.parse(response.getContentText()); 106 | } 107 | 108 | function gs_literal_(data) { 109 | if (data === undefined || data === null) { 110 | data = null 111 | } else if (Array.isArray(data)) { 112 | var s = "" 113 | for (var i=0; i 50) s = s.substring(0, 50) + "..." 146 | return s 147 | } 148 | 149 | function exec_(code, val, path, root) { 150 | for (var i=0; i < code.length; i++) { 151 | val = code[i].value(val, path, root); 152 | if (!val) break; 153 | } 154 | return val 155 | } 156 | 157 | function compile_path_(path) { 158 | var ops = [] 159 | var tokens = tokenize_(path.trim()) 160 | 161 | if (is_next_(tokens, "dollar")) { 162 | ops.push(new op_root_()) 163 | tokens.shift() 164 | } 165 | 166 | while (tokens.length > 0) { 167 | var tok = tokens.shift() 168 | var type = tok[0] 169 | if (type == "dot" || type == "at") { 170 | continue; 171 | } else if (type == "caret") { 172 | ops.push(new op_up_()) 173 | } else if (type == "tilde") { 174 | if (tokens.length > 0) { 175 | throw new Error("~ operator can only be used at the end of the path"); 176 | } 177 | ops.push(new op_propname_()) 178 | } else if (type == "string" || type == "identifier") { 179 | var s = decodeURIComponent(tok[1]) // Replace escaped commas 180 | ops.push(new op_down_(type == "string" ? s.slice(1, -1) : s)) 181 | } else if (type == "obracket") { 182 | if (!is_next_(tokens, "int")) { 183 | throw new Error("array index must be an integer"); 184 | } 185 | var idx = tokens.shift()[1] 186 | ops.push(new op_down_(parseInt(idx))) 187 | if (!is_next_(tokens, "cbracket")) { 188 | throw new Error("expected token ']'"); 189 | } 190 | tokens.shift() 191 | } else if (type == "dollar") { 192 | throw new Error("$ can only be used at the beginning of the path"); 193 | } 194 | } 195 | 196 | return ops 197 | } 198 | 199 | function op_root_() { 200 | this.value = function (val, path, root) { 201 | while (path.length > 1) { 202 | path.pop() 203 | } 204 | return root 205 | } 206 | } 207 | 208 | function op_up_() { 209 | this.value = function (val, path, root) { 210 | if (path.length > 1) { 211 | path.pop() 212 | } 213 | return resolve_(path, root) 214 | } 215 | } 216 | 217 | function op_down_(name) { 218 | this.value = function (val, path, root) { 219 | if (Array.isArray(val) || (typeof val === "object" && val !== null)) { 220 | path.push(name) 221 | return val[name] 222 | } else { 223 | return undefined 224 | } 225 | } 226 | } 227 | 228 | function op_propname_() { 229 | this.value = function (val, path, root) { 230 | if (path.length > 1) { 231 | return path[path.length-1] 232 | } else { 233 | return undefined 234 | } 235 | } 236 | } 237 | 238 | function resolve_(path, root) { 239 | var value = root 240 | for (i=1; i 0 && tokens[0][0] == ttype) { 321 | return true; 322 | } else { 323 | return false 324 | } 325 | } 326 | 327 | /*====================================================================================================================================* 328 | 329 | JSONPath query engine. Copied from: 330 | https://github.com/s3u/JSONPath 331 | 332 | *====================================================================================================================================*/ 333 | 334 | (function (global, factory) { 335 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 336 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 337 | (global = global || self, factory(global.JSONPath = {})); 338 | }(this, (function (exports) { 'use strict'; 339 | 340 | function _typeof(obj) { 341 | "@babel/helpers - typeof"; 342 | 343 | if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 344 | _typeof = function (obj) { 345 | return typeof obj; 346 | }; 347 | } else { 348 | _typeof = function (obj) { 349 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 350 | }; 351 | } 352 | 353 | return _typeof(obj); 354 | } 355 | 356 | function _classCallCheck(instance, Constructor) { 357 | if (!(instance instanceof Constructor)) { 358 | throw new TypeError("Cannot call a class as a function"); 359 | } 360 | } 361 | 362 | function _inherits(subClass, superClass) { 363 | if (typeof superClass !== "function" && superClass !== null) { 364 | throw new TypeError("Super expression must either be null or a function"); 365 | } 366 | 367 | subClass.prototype = Object.create(superClass && superClass.prototype, { 368 | constructor: { 369 | value: subClass, 370 | writable: true, 371 | configurable: true 372 | } 373 | }); 374 | if (superClass) _setPrototypeOf(subClass, superClass); 375 | } 376 | 377 | function _getPrototypeOf(o) { 378 | _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { 379 | return o.__proto__ || Object.getPrototypeOf(o); 380 | }; 381 | return _getPrototypeOf(o); 382 | } 383 | 384 | function _setPrototypeOf(o, p) { 385 | _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 386 | o.__proto__ = p; 387 | return o; 388 | }; 389 | 390 | return _setPrototypeOf(o, p); 391 | } 392 | 393 | function isNativeReflectConstruct() { 394 | if (typeof Reflect === "undefined" || !Reflect.construct) return false; 395 | if (Reflect.construct.sham) return false; 396 | if (typeof Proxy === "function") return true; 397 | 398 | try { 399 | Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); 400 | return true; 401 | } catch (e) { 402 | return false; 403 | } 404 | } 405 | 406 | function _construct(Parent, args, Class) { 407 | if (isNativeReflectConstruct()) { 408 | _construct = Reflect.construct; 409 | } else { 410 | _construct = function _construct(Parent, args, Class) { 411 | var a = [null]; 412 | a.push.apply(a, args); 413 | var Constructor = Function.bind.apply(Parent, a); 414 | var instance = new Constructor(); 415 | if (Class) _setPrototypeOf(instance, Class.prototype); 416 | return instance; 417 | }; 418 | } 419 | 420 | return _construct.apply(null, arguments); 421 | } 422 | 423 | function _isNativeFunction(fn) { 424 | return Function.toString.call(fn).indexOf("[native code]") !== -1; 425 | } 426 | 427 | function _wrapNativeSuper(Class) { 428 | var _cache = typeof Map === "function" ? new Map() : undefined; 429 | 430 | _wrapNativeSuper = function _wrapNativeSuper(Class) { 431 | if (Class === null || !_isNativeFunction(Class)) return Class; 432 | 433 | if (typeof Class !== "function") { 434 | throw new TypeError("Super expression must either be null or a function"); 435 | } 436 | 437 | if (typeof _cache !== "undefined") { 438 | if (_cache.has(Class)) return _cache.get(Class); 439 | 440 | _cache.set(Class, Wrapper); 441 | } 442 | 443 | function Wrapper() { 444 | return _construct(Class, arguments, _getPrototypeOf(this).constructor); 445 | } 446 | 447 | Wrapper.prototype = Object.create(Class.prototype, { 448 | constructor: { 449 | value: Wrapper, 450 | enumerable: false, 451 | writable: true, 452 | configurable: true 453 | } 454 | }); 455 | return _setPrototypeOf(Wrapper, Class); 456 | }; 457 | 458 | return _wrapNativeSuper(Class); 459 | } 460 | 461 | function _assertThisInitialized(self) { 462 | if (self === void 0) { 463 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 464 | } 465 | 466 | return self; 467 | } 468 | 469 | function _possibleConstructorReturn(self, call) { 470 | if (call && (typeof call === "object" || typeof call === "function")) { 471 | return call; 472 | } 473 | 474 | return _assertThisInitialized(self); 475 | } 476 | 477 | function _toConsumableArray(arr) { 478 | return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); 479 | } 480 | 481 | function _arrayWithoutHoles(arr) { 482 | if (Array.isArray(arr)) { 483 | for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; 484 | 485 | return arr2; 486 | } 487 | } 488 | 489 | function _iterableToArray(iter) { 490 | if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); 491 | } 492 | 493 | function _nonIterableSpread() { 494 | throw new TypeError("Invalid attempt to spread non-iterable instance"); 495 | } 496 | 497 | /* eslint-disable prefer-named-capture-group */ 498 | // Disabled `prefer-named-capture-group` due to https://github.com/babel/babel/issues/8951#issuecomment-508045524 499 | // Only Node.JS has a process variable that is of [[Class]] process 500 | var supportsNodeVM = function supportsNodeVM() { 501 | try { 502 | return Object.prototype.toString.call(global.process) === '[object process]'; 503 | } catch (e) { 504 | return false; 505 | } 506 | }; 507 | 508 | var hasOwnProp = Object.prototype.hasOwnProperty; 509 | /** 510 | * @typedef {null|boolean|number|string|PlainObject|GenericArray} JSONObject 511 | */ 512 | 513 | /** 514 | * @callback ConditionCallback 515 | * @param {any} item 516 | * @returns {boolean} 517 | */ 518 | 519 | /** 520 | * Copy items out of one array into another. 521 | * @param {GenericArray} source Array with items to copy 522 | * @param {GenericArray} target Array to which to copy 523 | * @param {ConditionCallback} conditionCb Callback passed the current item; 524 | * will move item if evaluates to `true` 525 | * @returns {void} 526 | */ 527 | 528 | var moveToAnotherArray = function moveToAnotherArray(source, target, conditionCb) { 529 | var il = source.length; 530 | 531 | for (var i = 0; i < il; i++) { 532 | var item = source[i]; 533 | 534 | if (conditionCb(item)) { 535 | target.push(source.splice(i--, 1)[0]); 536 | } 537 | } 538 | }; 539 | 540 | JSONPath.nodeVMSupported = supportsNodeVM(); 541 | var vm = JSONPath.nodeVMSupported ? require('vm') : { 542 | /** 543 | * @param {string} expr Expression to evaluate 544 | * @param {PlainObject} context Object whose items will be added 545 | * to evaluation 546 | * @returns {any} Result of evaluated code 547 | */ 548 | runInNewContext: function runInNewContext(expr, context) { 549 | var keys = Object.keys(context); 550 | var funcs = []; 551 | moveToAnotherArray(keys, funcs, function (key) { 552 | return typeof context[key] === 'function'; 553 | }); 554 | var values = keys.map(function (vr, i) { 555 | return context[vr]; 556 | }); 557 | var funcString = funcs.reduce(function (s, func) { 558 | var fString = context[func].toString(); 559 | 560 | if (!/function/.test(fString)) { 561 | fString = 'function ' + fString; 562 | } 563 | 564 | return 'var ' + func + '=' + fString + ';' + s; 565 | }, ''); 566 | expr = funcString + expr; // Mitigate http://perfectionkills.com/global-eval-what-are-the-options/#new_function 567 | 568 | if (!expr.match(/(["'])use strict\1/) && !keys.includes('arguments')) { 569 | expr = 'var arguments = undefined;' + expr; 570 | } // Remove last semi so `return` will be inserted before 571 | // the previous one instead, allowing for the return 572 | // of a bare ending expression 573 | 574 | 575 | expr = expr.replace(/;[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]*$/, ''); // Insert `return` 576 | 577 | var lastStatementEnd = expr.lastIndexOf(';'); 578 | var code = lastStatementEnd > -1 ? expr.slice(0, lastStatementEnd + 1) + ' return ' + expr.slice(lastStatementEnd + 1) : ' return ' + expr; // eslint-disable-next-line no-new-func 579 | 580 | return _construct(Function, _toConsumableArray(keys).concat([code])).apply(void 0, _toConsumableArray(values)); 581 | } 582 | }; 583 | /** 584 | * Copies array and then pushes item into it. 585 | * @param {GenericArray} arr Array to copy and into which to push 586 | * @param {any} item Array item to add (to end) 587 | * @returns {GenericArray} Copy of the original array 588 | */ 589 | 590 | function push(arr, item) { 591 | arr = arr.slice(); 592 | arr.push(item); 593 | return arr; 594 | } 595 | /** 596 | * Copies array and then unshifts item into it. 597 | * @param {any} item Array item to add (to beginning) 598 | * @param {GenericArray} arr Array to copy and into which to unshift 599 | * @returns {GenericArray} Copy of the original array 600 | */ 601 | 602 | 603 | function unshift(item, arr) { 604 | arr = arr.slice(); 605 | arr.unshift(item); 606 | return arr; 607 | } 608 | /** 609 | * Caught when JSONPath is used without `new` but rethrown if with `new` 610 | * @extends Error 611 | */ 612 | 613 | 614 | var NewError = 615 | /*#__PURE__*/ 616 | function (_Error) { 617 | _inherits(NewError, _Error); 618 | 619 | /** 620 | * @param {any} value The evaluated scalar value 621 | */ 622 | function NewError(value) { 623 | var _this; 624 | 625 | _classCallCheck(this, NewError); 626 | 627 | _this = _possibleConstructorReturn(this, _getPrototypeOf(NewError).call(this, 'JSONPath should not be called with "new" (it prevents return ' + 'of (unwrapped) scalar values)')); 628 | _this.avoidNew = true; 629 | _this.value = value; 630 | _this.name = 'NewError'; 631 | return _this; 632 | } 633 | 634 | return NewError; 635 | }(_wrapNativeSuper(Error)); 636 | /** 637 | * @typedef {PlainObject} ReturnObject 638 | * @property {string} path 639 | * @property {JSONObject} value 640 | * @property {PlainObject|GenericArray} parent 641 | * @property {string} parentProperty 642 | */ 643 | 644 | /** 645 | * @callback JSONPathCallback 646 | * @param {string|PlainObject} preferredOutput 647 | * @param {"value"|"property"} type 648 | * @param {ReturnObject} fullRetObj 649 | * @returns {void} 650 | */ 651 | 652 | /** 653 | * @callback OtherTypeCallback 654 | * @param {JSONObject} val 655 | * @param {string} path 656 | * @param {PlainObject|GenericArray} parent 657 | * @param {string} parentPropName 658 | * @returns {boolean} 659 | */ 660 | 661 | /** 662 | * @typedef {PlainObject} JSONPathOptions 663 | * @property {JSON} json 664 | * @property {string|string[]} path 665 | * @property {"value"|"path"|"pointer"|"parent"|"parentProperty"|"all"} 666 | * [resultType="value"] 667 | * @property {boolean} [flatten=false] 668 | * @property {boolean} [wrap=true] 669 | * @property {PlainObject} [sandbox={}] 670 | * @property {boolean} [preventEval=false] 671 | * @property {PlainObject|GenericArray|null} [parent=null] 672 | * @property {string|null} [parentProperty=null] 673 | * @property {JSONPathCallback} [callback] 674 | * @property {OtherTypeCallback} [otherTypeCallback] Defaults to 675 | * function which throws on encountering `@other` 676 | * @property {boolean} [autostart=true] 677 | */ 678 | 679 | /** 680 | * @param {string|JSONPathOptions} opts If a string, will be treated as `expr` 681 | * @param {string} [expr] JSON path to evaluate 682 | * @param {JSON} [obj] JSON object to evaluate against 683 | * @param {JSONPathCallback} [callback] Passed 3 arguments: 1) desired payload 684 | * per `resultType`, 2) `"value"|"property"`, 3) Full returned object with 685 | * all payloads 686 | * @param {OtherTypeCallback} [otherTypeCallback] If `@other()` is at the end 687 | * of one's query, this will be invoked with the value of the item, its 688 | * path, its parent, and its parent's property name, and it should return 689 | * a boolean indicating whether the supplied value belongs to the "other" 690 | * type or not (or it may handle transformations and return `false`). 691 | * @returns {JSONPath} 692 | * @class 693 | */ 694 | 695 | 696 | function JSONPath(opts, expr, obj, callback, otherTypeCallback) { 697 | // eslint-disable-next-line no-restricted-syntax 698 | if (!(this instanceof JSONPath)) { 699 | try { 700 | return new JSONPath(opts, expr, obj, callback, otherTypeCallback); 701 | } catch (e) { 702 | if (!e.avoidNew) { 703 | throw e; 704 | } 705 | 706 | return e.value; 707 | } 708 | } 709 | 710 | if (typeof opts === 'string') { 711 | otherTypeCallback = callback; 712 | callback = obj; 713 | obj = expr; 714 | expr = opts; 715 | opts = null; 716 | } 717 | 718 | var optObj = opts && _typeof(opts) === 'object'; 719 | opts = opts || {}; 720 | this.json = opts.json || obj; 721 | this.path = opts.path || expr; 722 | this.resultType = opts.resultType && opts.resultType.toLowerCase() || 'value'; 723 | this.flatten = opts.flatten || false; 724 | this.wrap = hasOwnProp.call(opts, 'wrap') ? opts.wrap : true; 725 | this.sandbox = opts.sandbox || {}; 726 | this.preventEval = opts.preventEval || false; 727 | this.parent = opts.parent || null; 728 | this.parentProperty = opts.parentProperty || null; 729 | this.callback = opts.callback || callback || null; 730 | 731 | this.otherTypeCallback = opts.otherTypeCallback || otherTypeCallback || function () { 732 | throw new TypeError('You must supply an otherTypeCallback callback option ' + 'with the @other() operator.'); 733 | }; 734 | 735 | if (opts.autostart !== false) { 736 | var args = { 737 | path: optObj ? opts.path : expr 738 | }; 739 | 740 | if (!optObj) { 741 | args.json = obj; 742 | } else if ('json' in opts) { 743 | args.json = opts.json; 744 | } 745 | 746 | var ret = this.evaluate(args); 747 | 748 | if (!ret || _typeof(ret) !== 'object') { 749 | throw new NewError(ret); 750 | } 751 | 752 | return ret; 753 | } 754 | } // PUBLIC METHODS 755 | 756 | 757 | JSONPath.prototype.evaluate = function (expr, json, callback, otherTypeCallback) { 758 | var that = this; 759 | var currParent = this.parent, 760 | currParentProperty = this.parentProperty; 761 | var flatten = this.flatten, 762 | wrap = this.wrap; 763 | this.currResultType = this.resultType; 764 | this.currPreventEval = this.preventEval; 765 | this.currSandbox = this.sandbox; 766 | callback = callback || this.callback; 767 | this.currOtherTypeCallback = otherTypeCallback || this.otherTypeCallback; 768 | json = json || this.json; 769 | expr = expr || this.path; 770 | 771 | if (expr && _typeof(expr) === 'object' && !Array.isArray(expr)) { 772 | if (!expr.path && expr.path !== '') { 773 | throw new TypeError('You must supply a "path" property when providing an object ' + 'argument to JSONPath.evaluate().'); 774 | } 775 | 776 | if (!hasOwnProp.call(expr, 'json')) { 777 | throw new TypeError('You must supply a "json" property when providing an object ' + 'argument to JSONPath.evaluate().'); 778 | } 779 | 780 | var _expr = expr; 781 | json = _expr.json; 782 | flatten = hasOwnProp.call(expr, 'flatten') ? expr.flatten : flatten; 783 | this.currResultType = hasOwnProp.call(expr, 'resultType') ? expr.resultType : this.currResultType; 784 | this.currSandbox = hasOwnProp.call(expr, 'sandbox') ? expr.sandbox : this.currSandbox; 785 | wrap = hasOwnProp.call(expr, 'wrap') ? expr.wrap : wrap; 786 | this.currPreventEval = hasOwnProp.call(expr, 'preventEval') ? expr.preventEval : this.currPreventEval; 787 | callback = hasOwnProp.call(expr, 'callback') ? expr.callback : callback; 788 | this.currOtherTypeCallback = hasOwnProp.call(expr, 'otherTypeCallback') ? expr.otherTypeCallback : this.currOtherTypeCallback; 789 | currParent = hasOwnProp.call(expr, 'parent') ? expr.parent : currParent; 790 | currParentProperty = hasOwnProp.call(expr, 'parentProperty') ? expr.parentProperty : currParentProperty; 791 | expr = expr.path; 792 | } 793 | 794 | currParent = currParent || null; 795 | currParentProperty = currParentProperty || null; 796 | 797 | if (Array.isArray(expr)) { 798 | expr = JSONPath.toPathString(expr); 799 | } 800 | 801 | if (!expr && expr !== '' || !json) { 802 | return undefined; 803 | } 804 | 805 | this._obj = json; 806 | var exprList = JSONPath.toPathArray(expr); 807 | 808 | if (exprList[0] === '$' && exprList.length > 1) { 809 | exprList.shift(); 810 | } 811 | 812 | this._hasParentSelector = null; 813 | 814 | var result = this._trace(exprList, json, ['$'], currParent, currParentProperty, callback).filter(function (ea) { 815 | return ea && !ea.isParentSelector; 816 | }); 817 | 818 | if (!result.length) { 819 | return wrap ? [] : undefined; 820 | } 821 | 822 | if (!wrap && result.length === 1 && !result[0].hasArrExpr) { 823 | return this._getPreferredOutput(result[0]); 824 | } 825 | 826 | return result.reduce(function (rslt, ea) { 827 | var valOrPath = that._getPreferredOutput(ea); 828 | 829 | if (flatten && Array.isArray(valOrPath)) { 830 | rslt = rslt.concat(valOrPath); 831 | } else { 832 | rslt.push(valOrPath); 833 | } 834 | 835 | return rslt; 836 | }, []); 837 | }; // PRIVATE METHODS 838 | 839 | 840 | JSONPath.prototype._getPreferredOutput = function (ea) { 841 | var resultType = this.currResultType; 842 | 843 | switch (resultType) { 844 | default: 845 | throw new TypeError('Unknown result type'); 846 | 847 | case 'all': 848 | { 849 | var path = Array.isArray(ea.path) ? ea.path : JSONPath.toPathArray(ea.path); 850 | ea.pointer = JSONPath.toPointer(path); 851 | ea.path = typeof ea.path === 'string' ? ea.path : JSONPath.toPathString(ea.path); 852 | return ea; 853 | } 854 | 855 | case 'value': 856 | case 'parent': 857 | case 'parentProperty': 858 | return ea[resultType]; 859 | 860 | case 'path': 861 | return JSONPath.toPathString(ea[resultType]); 862 | 863 | case 'pointer': 864 | return JSONPath.toPointer(ea.path); 865 | } 866 | }; 867 | 868 | JSONPath.prototype._handleCallback = function (fullRetObj, callback, type) { 869 | if (callback) { 870 | var preferredOutput = this._getPreferredOutput(fullRetObj); 871 | 872 | fullRetObj.path = typeof fullRetObj.path === 'string' ? fullRetObj.path : JSONPath.toPathString(fullRetObj.path); // eslint-disable-next-line callback-return 873 | 874 | callback(preferredOutput, type, fullRetObj); 875 | } 876 | }; 877 | /** 878 | * 879 | * @param {string} expr 880 | * @param {JSONObject} val 881 | * @param {string} path 882 | * @param {PlainObject|GenericArray} parent 883 | * @param {string} parentPropName 884 | * @param {JSONPathCallback} callback 885 | * @param {boolean} hasArrExpr 886 | * @param {boolean} literalPriority 887 | * @returns {ReturnObject|ReturnObject[]} 888 | */ 889 | 890 | 891 | JSONPath.prototype._trace = function (expr, val, path, parent, parentPropName, callback, hasArrExpr, literalPriority) { 892 | // No expr to follow? return path and value as the result of 893 | // this trace branch 894 | var retObj; 895 | var that = this; 896 | 897 | if (!expr.length) { 898 | retObj = { 899 | path: path, 900 | value: val, 901 | parent: parent, 902 | parentProperty: parentPropName, 903 | hasArrExpr: hasArrExpr 904 | }; 905 | 906 | this._handleCallback(retObj, callback, 'value'); 907 | 908 | return retObj; 909 | } 910 | 911 | var loc = expr[0], 912 | x = expr.slice(1); // We need to gather the return value of recursive trace calls in order to 913 | // do the parent sel computation. 914 | 915 | var ret = []; 916 | /** 917 | * 918 | * @param {ReturnObject|ReturnObject[]} elems 919 | * @returns {void} 920 | */ 921 | 922 | function addRet(elems) { 923 | if (Array.isArray(elems)) { 924 | // This was causing excessive stack size in Node (with or 925 | // without Babel) against our performance test: 926 | // `ret.push(...elems);` 927 | elems.forEach(function (t) { 928 | ret.push(t); 929 | }); 930 | } else { 931 | ret.push(elems); 932 | } 933 | } 934 | 935 | if ((typeof loc !== 'string' || literalPriority) && val && hasOwnProp.call(val, loc)) { 936 | // simple case--directly follow property 937 | addRet(this._trace(x, val[loc], push(path, loc), val, loc, callback, hasArrExpr)); 938 | } else if (loc === '*') { 939 | // all child properties 940 | this._walk(loc, x, val, path, parent, parentPropName, callback, function (m, l, _x, v, p, par, pr, cb) { 941 | addRet(that._trace(unshift(m, _x), v, p, par, pr, cb, true, true)); 942 | }); 943 | } else if (loc === '..') { 944 | // all descendent parent properties 945 | // Check remaining expression with val's immediate children 946 | addRet(this._trace(x, val, path, parent, parentPropName, callback, hasArrExpr)); 947 | 948 | this._walk(loc, x, val, path, parent, parentPropName, callback, function (m, l, _x, v, p, par, pr, cb) { 949 | // We don't join m and x here because we only want parents, 950 | // not scalar values 951 | if (_typeof(v[m]) === 'object') { 952 | // Keep going with recursive descent on val's 953 | // object children 954 | addRet(that._trace(unshift(l, _x), v[m], push(p, m), v, m, cb, true)); 955 | } 956 | }); // The parent sel computation is handled in the frame above using the 957 | // ancestor object of val 958 | 959 | } else if (loc === '^') { 960 | // This is not a final endpoint, so we do not invoke the callback here 961 | this._hasParentSelector = true; 962 | return { 963 | path: path.slice(0, -1), 964 | expr: x, 965 | isParentSelector: true 966 | }; 967 | } else if (loc === '~') { 968 | // property name 969 | retObj = { 970 | path: push(path, loc), 971 | value: parentPropName, 972 | parent: parent, 973 | parentProperty: null 974 | }; 975 | 976 | this._handleCallback(retObj, callback, 'property'); 977 | 978 | return retObj; 979 | } else if (loc === '$') { 980 | // root only 981 | addRet(this._trace(x, val, path, null, null, callback, hasArrExpr)); 982 | } else if (/^(\x2D?[0-9]*):(\x2D?[0-9]*):?([0-9]*)$/.test(loc)) { 983 | // [start:end:step] Python slice syntax 984 | addRet(this._slice(loc, x, val, path, parent, parentPropName, callback)); 985 | } else if (loc.indexOf('?(') === 0) { 986 | // [?(expr)] (filtering) 987 | if (this.currPreventEval) { 988 | throw new Error('Eval [?(expr)] prevented in JSONPath expression.'); 989 | } 990 | 991 | this._walk(loc, x, val, path, parent, parentPropName, callback, function (m, l, _x, v, p, par, pr, cb) { 992 | if (that._eval(l.replace(/^\?\(((?:[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*?)\)$/, '$1'), v[m], m, p, par, pr)) { 993 | addRet(that._trace(unshift(m, _x), v, p, par, pr, cb, true)); 994 | } 995 | }); 996 | } else if (loc[0] === '(') { 997 | // [(expr)] (dynamic property/index) 998 | if (this.currPreventEval) { 999 | throw new Error('Eval [(expr)] prevented in JSONPath expression.'); 1000 | } // As this will resolve to a property name (but we don't know it 1001 | // yet), property and parent information is relative to the 1002 | // parent of the property to which this expression will resolve 1003 | 1004 | 1005 | addRet(this._trace(unshift(this._eval(loc, val, path[path.length - 1], path.slice(0, -1), parent, parentPropName), x), val, path, parent, parentPropName, callback, hasArrExpr)); 1006 | } else if (loc[0] === '@') { 1007 | // value type: @boolean(), etc. 1008 | var addType = false; 1009 | var valueType = loc.slice(1, -2); 1010 | 1011 | switch (valueType) { 1012 | /* istanbul ignore next */ 1013 | default: 1014 | throw new TypeError('Unknown value type ' + valueType); 1015 | 1016 | case 'scalar': 1017 | if (!val || !['object', 'function'].includes(_typeof(val))) { 1018 | addType = true; 1019 | } 1020 | 1021 | break; 1022 | 1023 | case 'boolean': 1024 | case 'string': 1025 | case 'undefined': 1026 | case 'function': 1027 | // eslint-disable-next-line valid-typeof 1028 | if (_typeof(val) === valueType) { 1029 | addType = true; 1030 | } 1031 | 1032 | break; 1033 | 1034 | case 'number': 1035 | // eslint-disable-next-line valid-typeof 1036 | if (_typeof(val) === valueType && isFinite(val)) { 1037 | addType = true; 1038 | } 1039 | 1040 | break; 1041 | 1042 | case 'nonFinite': 1043 | if (typeof val === 'number' && !isFinite(val)) { 1044 | addType = true; 1045 | } 1046 | 1047 | break; 1048 | 1049 | case 'object': 1050 | // eslint-disable-next-line valid-typeof 1051 | if (val && _typeof(val) === valueType) { 1052 | addType = true; 1053 | } 1054 | 1055 | break; 1056 | 1057 | case 'array': 1058 | if (Array.isArray(val)) { 1059 | addType = true; 1060 | } 1061 | 1062 | break; 1063 | 1064 | case 'other': 1065 | addType = this.currOtherTypeCallback(val, path, parent, parentPropName); 1066 | break; 1067 | 1068 | case 'integer': 1069 | if (val === Number(val) && isFinite(val) && !(val % 1)) { 1070 | addType = true; 1071 | } 1072 | 1073 | break; 1074 | 1075 | case 'null': 1076 | if (val === null) { 1077 | addType = true; 1078 | } 1079 | 1080 | break; 1081 | } 1082 | 1083 | if (addType) { 1084 | retObj = { 1085 | path: path, 1086 | value: val, 1087 | parent: parent, 1088 | parentProperty: parentPropName 1089 | }; 1090 | 1091 | this._handleCallback(retObj, callback, 'value'); 1092 | 1093 | return retObj; 1094 | } // `-escaped property 1095 | 1096 | } else if (loc[0] === '`' && val && hasOwnProp.call(val, loc.slice(1))) { 1097 | var locProp = loc.slice(1); 1098 | addRet(this._trace(x, val[locProp], push(path, locProp), val, locProp, callback, hasArrExpr, true)); 1099 | } else if (loc.includes(',')) { 1100 | // [name1,name2,...] 1101 | var parts = loc.split(','); 1102 | var _iteratorNormalCompletion = true; 1103 | var _didIteratorError = false; 1104 | var _iteratorError = undefined; 1105 | 1106 | try { 1107 | for (var _iterator = parts[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 1108 | var part = _step.value; 1109 | addRet(this._trace(unshift(part, x), val, path, parent, parentPropName, callback, true)); 1110 | } // simple case--directly follow property 1111 | 1112 | } catch (err) { 1113 | _didIteratorError = true; 1114 | _iteratorError = err; 1115 | } finally { 1116 | try { 1117 | if (!_iteratorNormalCompletion && _iterator["return"] != null) { 1118 | _iterator["return"](); 1119 | } 1120 | } finally { 1121 | if (_didIteratorError) { 1122 | throw _iteratorError; 1123 | } 1124 | } 1125 | } 1126 | } else if (!literalPriority && val && hasOwnProp.call(val, loc)) { 1127 | addRet(this._trace(x, val[loc], push(path, loc), val, loc, callback, hasArrExpr, true)); 1128 | } // We check the resulting values for parent selections. For parent 1129 | // selections we discard the value object and continue the trace with the 1130 | // current val object 1131 | 1132 | 1133 | if (this._hasParentSelector) { 1134 | for (var t = 0; t < ret.length; t++) { 1135 | var rett = ret[t]; 1136 | 1137 | if (rett && rett.isParentSelector) { 1138 | var tmp = that._trace(rett.expr, val, rett.path, parent, parentPropName, callback, hasArrExpr); 1139 | 1140 | if (Array.isArray(tmp)) { 1141 | ret[t] = tmp[0]; 1142 | var tl = tmp.length; 1143 | 1144 | for (var tt = 1; tt < tl; tt++) { 1145 | t++; 1146 | ret.splice(t, 0, tmp[tt]); 1147 | } 1148 | } else { 1149 | ret[t] = tmp; 1150 | } 1151 | } 1152 | } 1153 | } 1154 | 1155 | return ret; 1156 | }; 1157 | 1158 | JSONPath.prototype._walk = function (loc, expr, val, path, parent, parentPropName, callback, f) { 1159 | if (Array.isArray(val)) { 1160 | var n = val.length; 1161 | 1162 | for (var i = 0; i < n; i++) { 1163 | f(i, loc, expr, val, path, parent, parentPropName, callback); 1164 | } 1165 | } else if (val && _typeof(val) === 'object') { 1166 | Object.keys(val).forEach(function (m) { 1167 | f(m, loc, expr, val, path, parent, parentPropName, callback); 1168 | }); 1169 | } 1170 | }; 1171 | 1172 | JSONPath.prototype._slice = function (loc, expr, val, path, parent, parentPropName, callback) { 1173 | if (!Array.isArray(val)) { 1174 | return undefined; 1175 | } 1176 | 1177 | var len = val.length, 1178 | parts = loc.split(':'), 1179 | step = parts[2] && parseInt(parts[2]) || 1; 1180 | var start = parts[0] && parseInt(parts[0]) || 0, 1181 | end = parts[1] && parseInt(parts[1]) || len; 1182 | start = start < 0 ? Math.max(0, start + len) : Math.min(len, start); 1183 | end = end < 0 ? Math.max(0, end + len) : Math.min(len, end); 1184 | var ret = []; 1185 | 1186 | for (var i = start; i < end; i += step) { 1187 | var tmp = this._trace(unshift(i, expr), val, path, parent, parentPropName, callback, true); // Should only be possible to be an array here since first part of 1188 | // ``unshift(i, expr)` passed in above would not be empty, nor `~`, 1189 | // nor begin with `@` (as could return objects) 1190 | // This was causing excessive stack size in Node (with or 1191 | // without Babel) against our performance test: `ret.push(...tmp);` 1192 | 1193 | 1194 | tmp.forEach(function (t) { 1195 | ret.push(t); 1196 | }); 1197 | } 1198 | 1199 | return ret; 1200 | }; 1201 | 1202 | JSONPath.prototype._eval = function (code, _v, _vname, path, parent, parentPropName) { 1203 | if (!this._obj || !_v) { 1204 | return false; 1205 | } 1206 | 1207 | if (code.includes('@parentProperty')) { 1208 | this.currSandbox._$_parentProperty = parentPropName; 1209 | code = code.replace(/@parentProperty/g, '_$_parentProperty'); 1210 | } 1211 | 1212 | if (code.includes('@parent')) { 1213 | this.currSandbox._$_parent = parent; 1214 | code = code.replace(/@parent/g, '_$_parent'); 1215 | } 1216 | 1217 | if (code.includes('@property')) { 1218 | this.currSandbox._$_property = _vname; 1219 | code = code.replace(/@property/g, '_$_property'); 1220 | } 1221 | 1222 | if (code.includes('@path')) { 1223 | this.currSandbox._$_path = JSONPath.toPathString(path.concat([_vname])); 1224 | code = code.replace(/@path/g, '_$_path'); 1225 | } 1226 | 1227 | if (code.includes('@root')) { 1228 | this.currSandbox._$_root = this.json; 1229 | code = code.replace(/@root/g, '_$_root'); 1230 | } 1231 | 1232 | if (code.match(/@([\t-\r \)\.\[\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF])/)) { 1233 | this.currSandbox._$_v = _v; 1234 | code = code.replace(/@([\t-\r \)\.\[\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF])/g, '_$_v$1'); 1235 | } 1236 | 1237 | try { 1238 | return vm.runInNewContext(code, this.currSandbox); 1239 | } catch (e) { 1240 | // eslint-disable-next-line no-console 1241 | console.log(e); 1242 | throw new Error('jsonPath: ' + e.message + ': ' + code); 1243 | } 1244 | }; // PUBLIC CLASS PROPERTIES AND METHODS 1245 | // Could store the cache object itself 1246 | 1247 | 1248 | JSONPath.cache = {}; 1249 | /** 1250 | * @param {string[]} pathArr Array to convert 1251 | * @returns {string} The path string 1252 | */ 1253 | 1254 | JSONPath.toPathString = function (pathArr) { 1255 | var x = pathArr, 1256 | n = x.length; 1257 | var p = '$'; 1258 | 1259 | for (var i = 1; i < n; i++) { 1260 | if (!/^(~|\^|@(?:[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*?\(\))$/.test(x[i])) { 1261 | p += /^[\*0-9]+$/.test(x[i]) ? '[' + x[i] + ']' : "['" + x[i] + "']"; 1262 | } 1263 | } 1264 | 1265 | return p; 1266 | }; 1267 | /** 1268 | * @param {string} pointer JSON Path 1269 | * @returns {string} JSON Pointer 1270 | */ 1271 | 1272 | 1273 | JSONPath.toPointer = function (pointer) { 1274 | var x = pointer, 1275 | n = x.length; 1276 | var p = ''; 1277 | 1278 | for (var i = 1; i < n; i++) { 1279 | if (!/^(~|\^|@(?:[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*?\(\))$/.test(x[i])) { 1280 | p += '/' + x[i].toString().replace(/~/g, '~0').replace(/\//g, '~1'); 1281 | } 1282 | } 1283 | 1284 | return p; 1285 | }; 1286 | /** 1287 | * @param {string} expr Expression to convert 1288 | * @returns {string[]} 1289 | */ 1290 | 1291 | 1292 | JSONPath.toPathArray = function (expr) { 1293 | var cache = JSONPath.cache; 1294 | 1295 | if (cache[expr]) { 1296 | return cache[expr].concat(); 1297 | } 1298 | 1299 | var subx = []; 1300 | var normalized = expr // Properties 1301 | .replace(/@(?:null|boolean|number|string|integer|undefined|nonFinite|scalar|array|object|function|other)\(\)/g, ';$&;') // Parenthetical evaluations (filtering and otherwise), directly 1302 | // within brackets or single quotes 1303 | .replace(/['\[](\??\((?:[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*?\))['\]]/g, function ($0, $1) { 1304 | return '[#' + (subx.push($1) - 1) + ']'; 1305 | }) // Escape periods and tildes within properties 1306 | .replace(/\['((?:[\0-&\(-\\\^-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*)'\]/g, function ($0, prop) { 1307 | return "['" + prop.replace(/\./g, '%@%').replace(/~/g, '%%@@%%') + "']"; 1308 | }) // Properties operator 1309 | .replace(/~/g, ';~;') // Split by property boundaries 1310 | .replace(/'?\.'?(?!(?:[\0-Z\\-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*\])|\['?/g, ';') // Reinsert periods within properties 1311 | .replace(/%@%/g, '.') // Reinsert tildes within properties 1312 | .replace(/%%@@%%/g, '~') // Parent 1313 | .replace(/(?:;)?(\^+)(?:;)?/g, function ($0, ups) { 1314 | return ';' + ups.split('').join(';') + ';'; 1315 | }) // Descendents 1316 | .replace(/;;;|;;/g, ';..;') // Remove trailing 1317 | .replace(/;$|'?\]|'$/g, ''); 1318 | var exprList = normalized.split(';').map(function (exp) { 1319 | var match = exp.match(/#([0-9]+)/); 1320 | return !match || !match[1] ? exp : subx[match[1]]; 1321 | }); 1322 | cache[expr] = exprList; 1323 | return cache[expr]; 1324 | }; 1325 | 1326 | exports.JSONPath = JSONPath; 1327 | 1328 | Object.defineProperty(exports, '__esModule', { value: true }); 1329 | 1330 | }))); 1331 | 1332 | if (typeof exports !== "undefined") { 1333 | exports.IMPORTJSONAPI = IMPORTJSONAPI 1334 | } 1335 | --------------------------------------------------------------------------------