Advertisement
Guest User

ImportJSON Google Sheets script

a guest
Sep 21st, 2017
9,991
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.77 KB | None | 0 0
  1. /**
  2. * Retrieves all the rows in the active spreadsheet that contain data and logs the
  3. * values for each row.
  4. * For more information on using the Spreadsheet API, see
  5. * https://developers.google.com/apps-script/service_spreadsheet
  6. */
  7. function readRows() {
  8. var sheet = SpreadsheetApp.getActiveSheet();
  9. var rows = sheet.getDataRange();
  10. var numRows = rows.getNumRows();
  11. var values = rows.getValues();
  12.  
  13. for (var i = 0; i <= numRows - 1; i++) {
  14. var row = values[i];
  15. Logger.log(row);
  16. }
  17. SpreadsheetApp.getActiveSpreadsheet().getRange('A1').setValue(new Date().toTimeString());
  18. };
  19.  
  20. /**
  21. * Adds a custom menu to the active spreadsheet, containing a single menu item
  22. * for invoking the readRows() function specified above.
  23. * The onOpen() function, when defined, is automatically invoked whenever the
  24. * spreadsheet is opened.
  25. * For more information on using the Spreadsheet API, see
  26. * https://developers.google.com/apps-script/service_spreadsheet
  27. */
  28. function onOpen() {
  29. var sheet = SpreadsheetApp.getActiveSpreadsheet();
  30. var entries = [{
  31. name : "Read Data",
  32. functionName : "readRows"
  33. }];
  34. sheet.addMenu("Script Center Menu", entries);
  35. };
  36.  
  37. /*====================================================================================================================================*
  38. ImportJSON by Trevor Lohrbeer (@FastFedora)
  39. ====================================================================================================================================
  40. Version: 1.1
  41. Project Page: http://blog.fastfedora.com/projects/import-json
  42. Copyright: (c) 2012 by Trevor Lohrbeer
  43. License: GNU General Public License, version 3 (GPL-3.0)
  44. http://www.opensource.org/licenses/gpl-3.0.html
  45. ------------------------------------------------------------------------------------------------------------------------------------
  46. A library for importing JSON feeds into Google spreadsheets. Functions include:
  47. ImportJSON For use by end users to import a JSON feed from a URL
  48. ImportJSONAdvanced For use by script developers to easily extend the functionality of this library
  49. Future enhancements may include:
  50. - Support for a real XPath like syntax similar to ImportXML for the query parameter
  51. - Support for OAuth authenticated APIs
  52. Or feel free to write these and add on to the library yourself!
  53. ------------------------------------------------------------------------------------------------------------------------------------
  54. Changelog:
  55.  
  56. 1.1 Added support for the noHeaders option
  57. 1.0 Initial release
  58. *====================================================================================================================================*/
  59. /**
  60. * Imports a JSON feed and returns the results to be inserted into a Google Spreadsheet. The JSON feed is flattened to create
  61. * a two-dimensional array. The first row contains the headers, with each column header indicating the path to that data in
  62. * the JSON feed. The remaining rows contain the data.
  63. *
  64. * By default, data gets transformed so it looks more like a normal data import. Specifically:
  65. *
  66. * - Data from parent JSON elements gets inherited to their child elements, so rows representing child elements contain the values
  67. * of the rows representing their parent elements.
  68. * - Values longer than 256 characters get truncated.
  69. * - Headers have slashes converted to spaces, common prefixes removed and the resulting text converted to title case.
  70. *
  71. * To change this behavior, pass in one of these values in the options parameter:
  72. *
  73. * noInherit: Don't inherit values from parent elements
  74. * noTruncate: Don't truncate values
  75. * rawHeaders: Don't prettify headers
  76. * noHeaders: Don't include headers, only the data
  77. * debugLocation: Prepend each value with the row & column it belongs in
  78. *
  79. * For example:
  80. *
  81. * =ImportJSON("http://gdata.youtube.com/feeds/api/standardfeeds/most_popular?v=2&alt=json", "/feed/entry/title,/feed/entry/content",
  82. * "noInherit,noTruncate,rawHeaders")
  83. *
  84. * @param {url} the URL to a public JSON feed
  85. * @param {query} a comma-separated lists of paths to import. Any path starting with one of these paths gets imported.
  86. * @param {options} a comma-separated list of options that alter processing of the data
  87. *
  88. * @return a two-dimensional array containing the data, with the first row containing headers
  89. * @customfunction
  90. **/
  91. function ImportJSON(url, query, options, datetime) {
  92. return ImportJSONAdvanced(url, query, options, includeXPath_, defaultTransform_, datetime);
  93. }
  94.  
  95. /**
  96. * An advanced version of ImportJSON designed to be easily extended by a script. This version cannot be called from within a
  97. * spreadsheet.
  98. *
  99. * Imports a JSON feed and returns the results to be inserted into a Google Spreadsheet. The JSON feed is flattened to create
  100. * a two-dimensional array. The first row contains the headers, with each column header indicating the path to that data in
  101. * the JSON feed. The remaining rows contain the data.
  102. *
  103. * Use the include and transformation functions to determine what to include in the import and how to transform the data after it is
  104. * imported.
  105. *
  106. * For example:
  107. *
  108. * =ImportJSON("http://gdata.youtube.com/feeds/api/standardfeeds/most_popular?v=2&alt=json",
  109. * "/feed/entry",
  110. * function (query, path) { return path.indexOf(query) == 0; },
  111. * function (data, row, column) { data[row][column] = data[row][column].toString().substr(0, 100); } )
  112. *
  113. * In this example, the import function checks to see if the path to the data being imported starts with the query. The transform
  114. * function takes the data and truncates it. For more robust versions of these functions, see the internal code of this library.
  115. *
  116. * @param {url} the URL to a public JSON feed
  117. * @param {query} the query passed to the include function
  118. * @param {options} a comma-separated list of options that may alter processing of the data
  119. * @param {includeFunc} a function with the signature func(query, path, options) that returns true if the data element at the given path
  120. * should be included or false otherwise.
  121. * @param {transformFunc} a function with the signature func(data, row, column, options) where data is a 2-dimensional array of the data
  122. * and row & column are the current row and column being processed. Any return value is ignored. Note that row 0
  123. * contains the headers for the data, so test for row==0 to process headers only.
  124. *
  125. * @return a two-dimensional array containing the data, with the first row containing headers
  126. **/
  127. function ImportJSONAdvanced(url, query, options, includeFunc, transformFunc, datetime) {
  128. var jsondata = UrlFetchApp.fetch(url);
  129. var object = JSON.parse(jsondata.getContentText());
  130.  
  131. return parseJSONObject_(object, query, options, includeFunc, transformFunc);
  132. }
  133.  
  134. /**
  135. * Encodes the given value to use within a URL.
  136. *
  137. * @param {value} the value to be encoded
  138. *
  139. * @return the value encoded using URL percent-encoding
  140. */
  141. function URLEncode(value) {
  142. return encodeURIComponent(value.toString());
  143. }
  144.  
  145. /**
  146. * Parses a JSON object and returns a two-dimensional array containing the data of that object.
  147. */
  148. function parseJSONObject_(object, query, options, includeFunc, transformFunc) {
  149. var headers = new Array();
  150. var data = new Array();
  151.  
  152. if (query && !Array.isArray(query) && query.toString().indexOf(",") != -1) {
  153. query = query.toString().split(",");
  154. }
  155.  
  156. if (options) {
  157. options = options.toString().split(",");
  158. }
  159.  
  160. parseData_(headers, data, "", 1, object, query, options, includeFunc);
  161. parseHeaders_(headers, data);
  162. transformData_(data, options, transformFunc);
  163.  
  164. return hasOption_(options, "noHeaders") ? (data.length > 1 ? data.slice(1) : new Array()) : data;
  165. }
  166.  
  167. /**
  168. * Parses the data contained within the given value and inserts it into the data two-dimensional array starting at the rowIndex.
  169. * If the data is to be inserted into a new column, a new header is added to the headers array. The value can be an object,
  170. * array or scalar value.
  171. *
  172. * If the value is an object, it's properties are iterated through and passed back into this function with the name of each
  173. * property extending the path. For instance, if the object contains the property "entry" and the path passed in was "/feed",
  174. * this function is called with the value of the entry property and the path "/feed/entry".
  175. *
  176. * If the value is an array containing other arrays or objects, each element in the array is passed into this function with
  177. * the rowIndex incremeneted for each element.
  178. *
  179. * If the value is an array containing only scalar values, those values are joined together and inserted into the data array as
  180. * a single value.
  181. *
  182. * If the value is a scalar, the value is inserted directly into the data array.
  183. */
  184. function parseData_(headers, data, path, rowIndex, value, query, options, includeFunc) {
  185. var dataInserted = false;
  186.  
  187. if (isObject_(value)) {
  188. for (key in value) {
  189. if (parseData_(headers, data, path + "/" + key, rowIndex, value[key], query, options, includeFunc)) {
  190. dataInserted = true;
  191. }
  192. }
  193. } else if (Array.isArray(value) && isObjectArray_(value)) {
  194. for (var i = 0; i < value.length; i++) {
  195. if (parseData_(headers, data, path, rowIndex, value[i], query, options, includeFunc)) {
  196. dataInserted = true;
  197. rowIndex++;
  198. }
  199. }
  200. } else if (!includeFunc || includeFunc(query, path, options)) {
  201. // Handle arrays containing only scalar values
  202. if (Array.isArray(value)) {
  203. value = value.join();
  204. }
  205.  
  206. // Insert new row if one doesn't already exist
  207. if (!data[rowIndex]) {
  208. data[rowIndex] = new Array();
  209. }
  210.  
  211. // Add a new header if one doesn't exist
  212. if (!headers[path] && headers[path] != 0) {
  213. headers[path] = Object.keys(headers).length;
  214. }
  215.  
  216. // Insert the data
  217. data[rowIndex][headers[path]] = value;
  218. dataInserted = true;
  219. }
  220.  
  221. return dataInserted;
  222. }
  223.  
  224. /**
  225. * Parses the headers array and inserts it into the first row of the data array.
  226. */
  227. function parseHeaders_(headers, data) {
  228. data[0] = new Array();
  229.  
  230. for (key in headers) {
  231. data[0][headers[key]] = key;
  232. }
  233. }
  234.  
  235. /**
  236. * Applies the transform function for each element in the data array, going through each column of each row.
  237. */
  238. function transformData_(data, options, transformFunc) {
  239. for (var i = 0; i < data.length; i++) {
  240. for (var j = 0; j < data[i].length; j++) {
  241. transformFunc(data, i, j, options);
  242. }
  243. }
  244. }
  245.  
  246. /**
  247. * Returns true if the given test value is an object; false otherwise.
  248. */
  249. function isObject_(test) {
  250. return Object.prototype.toString.call(test) === '[object Object]';
  251. }
  252.  
  253. /**
  254. * Returns true if the given test value is an array containing at least one object; false otherwise.
  255. */
  256. function isObjectArray_(test) {
  257. for (var i = 0; i < test.length; i++) {
  258. if (isObject_(test[i])) {
  259. return true;
  260. }
  261. }
  262.  
  263. return false;
  264. }
  265.  
  266. /**
  267. * Returns true if the given query applies to the given path.
  268. */
  269. function includeXPath_(query, path, options) {
  270. if (!query) {
  271. return true;
  272. } else if (Array.isArray(query)) {
  273. for (var i = 0; i < query.length; i++) {
  274. if (applyXPathRule_(query[i], path, options)) {
  275. return true;
  276. }
  277. }
  278. } else {
  279. return applyXPathRule_(query, path, options);
  280. }
  281.  
  282. return false;
  283. };
  284.  
  285. /**
  286. * Returns true if the rule applies to the given path.
  287. */
  288. function applyXPathRule_(rule, path, options) {
  289. return path.indexOf(rule) == 0;
  290. }
  291.  
  292. /**
  293. * By default, this function transforms the value at the given row & column so it looks more like a normal data import. Specifically:
  294. *
  295. * - Data from parent JSON elements gets inherited to their child elements, so rows representing child elements contain the values
  296. * of the rows representing their parent elements.
  297. * - Values longer than 256 characters get truncated.
  298. * - Values in row 0 (headers) have slashes converted to spaces, common prefixes removed and the resulting text converted to title
  299. * case.
  300. *
  301. * To change this behavior, pass in one of these values in the options parameter:
  302. *
  303. * noInherit: Don't inherit values from parent elements
  304. * noTruncate: Don't truncate values
  305. * rawHeaders: Don't prettify headers
  306. * debugLocation: Prepend each value with the row & column it belongs in
  307. */
  308. function defaultTransform_(data, row, column, options) {
  309. if (!data[row][column]) {
  310. if (row < 2 || hasOption_(options, "noInherit")) {
  311. data[row][column] = "";
  312. } else {
  313. data[row][column] = data[row-1][column];
  314. }
  315. }
  316.  
  317. if (!hasOption_(options, "rawHeaders") && row == 0) {
  318. if (column == 0 && data[row].length > 1) {
  319. removeCommonPrefixes_(data, row);
  320. }
  321.  
  322. data[row][column] = toTitleCase_(data[row][column].toString().replace(/[\/\_]/g, " "));
  323. }
  324.  
  325. if (!hasOption_(options, "noTruncate") && data[row][column]) {
  326. data[row][column] = data[row][column].toString().substr(0, 256);
  327. }
  328.  
  329. if (hasOption_(options, "debugLocation")) {
  330. data[row][column] = "[" + row + "," + column + "]" + data[row][column];
  331. }
  332. }
  333.  
  334. /**
  335. * If all the values in the given row share the same prefix, remove that prefix.
  336. */
  337. function removeCommonPrefixes_(data, row) {
  338. var matchIndex = data[row][0].length;
  339.  
  340. for (var i = 1; i < data[row].length; i++) {
  341. matchIndex = findEqualityEndpoint_(data[row][i-1], data[row][i], matchIndex);
  342.  
  343. if (matchIndex == 0) {
  344. return;
  345. }
  346. }
  347.  
  348. for (var i = 0; i < data[row].length; i++) {
  349. data[row][i] = data[row][i].substring(matchIndex, data[row][i].length);
  350. }
  351. }
  352.  
  353. /**
  354. * Locates the index where the two strings values stop being equal, stopping automatically at the stopAt index.
  355. */
  356. function findEqualityEndpoint_(string1, string2, stopAt) {
  357. if (!string1 || !string2) {
  358. return -1;
  359. }
  360.  
  361. var maxEndpoint = Math.min(stopAt, string1.length, string2.length);
  362.  
  363. for (var i = 0; i < maxEndpoint; i++) {
  364. if (string1.charAt(i) != string2.charAt(i)) {
  365. return i;
  366. }
  367. }
  368.  
  369. return maxEndpoint;
  370. }
  371.  
  372.  
  373. /**
  374. * Converts the text to title case.
  375. */
  376. function toTitleCase_(text) {
  377. if (text == null) {
  378. return null;
  379. }
  380.  
  381. return text.replace(/\w\S*/g, function(word) { return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase(); });
  382. }
  383.  
  384. /**
  385. * Returns true if the given set of options contains the given option.
  386. */
  387. function hasOption_(options, option) {
  388. return options && options.indexOf(option) >= 0;
  389. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement