Pro_Unit

Sheet To JSON

Apr 8th, 2020
328
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // Includes functions for exporting active sheet or all sheets as JSON object (also Python object syntax compatible).
  2. // Tweak the makePrettyJSON_ function to customize what kind of JSON to export.
  3.  
  4. var FORMAT_ONELINE   = 'One-line';
  5. var FORMAT_MULTILINE = 'Multi-line';
  6. var FORMAT_PRETTY    = 'Pretty';
  7.  
  8. var LANGUAGE_JS      = 'JavaScript';
  9. var LANGUAGE_PYTHON  = 'Python';
  10.  
  11. var STRUCTURE_LIST = 'List';
  12. var STRUCTURE_HASH = 'Hash (keyed by "id" column)';
  13.  
  14. /* Defaults for this particular spreadsheet, change as desired */
  15. var DEFAULT_FORMAT = FORMAT_PRETTY;
  16. var DEFAULT_LANGUAGE = LANGUAGE_JS;
  17. var DEFAULT_STRUCTURE = STRUCTURE_LIST;
  18.  
  19.  
  20. function onOpen() {
  21.   var ss = SpreadsheetApp.getActiveSpreadsheet();
  22.   var menuEntries = [
  23.     {name: "Export JSON for this sheet", functionName: "exportSheet"},
  24.     {name: "Export JSON for all sheets", functionName: "exportAllSheets"}
  25.   ];
  26.   ss.addMenu("Export JSON", menuEntries);
  27. }
  28.  
  29. function makeLabel(app, text, id) {
  30.   var lb = app.createLabel(text);
  31.   if (id) lb.setId(id);
  32.   return lb;
  33. }
  34.  
  35. function makeListBox(app, name, items) {
  36.   var listBox = app.createListBox().setId(name).setName(name);
  37.   listBox.setVisibleItemCount(1);
  38.  
  39.   var cache = CacheService.getPublicCache();
  40.   var selectedValue = cache.get(name);
  41.   Logger.log(selectedValue);
  42.   for (var i = 0; i < items.length; i++) {
  43.     listBox.addItem(items[i]);
  44.     if (items[1] == selectedValue) {
  45.       listBox.setSelectedIndex(i);
  46.     }
  47.   }
  48.   return listBox;
  49. }
  50.  
  51. function makeButton(app, parent, name, callback) {
  52.   var button = app.createButton(name);
  53.   app.add(button);
  54.   var handler = app.createServerClickHandler(callback).addCallbackElement(parent);;
  55.   button.addClickHandler(handler);
  56.   return button;
  57. }
  58.  
  59. function makeTextBox(app, name) {
  60.   var textArea    = app.createTextArea().setWidth('100%').setHeight('200px').setId(name).setName(name);
  61.   return textArea;
  62. }
  63.  
  64. function exportAllSheets(e) {
  65.  
  66.   var ss = SpreadsheetApp.getActiveSpreadsheet();
  67.   var sheets = ss.getSheets();
  68.   var sheetsData = {};
  69.   for (var i = 0; i < sheets.length; i++) {
  70.     var sheet = sheets[i];
  71.     var rowsData = getRowsData_(sheet, getExportOptions(e));
  72.     var sheetName = sheet.getName();
  73.     sheetsData[sheetName] = rowsData;
  74.   }
  75.   var json = makeJSON_(sheetsData, getExportOptions(e));
  76.   displayText_(json);
  77. }
  78.  
  79. function exportSheet(e) {
  80.   var ss = SpreadsheetApp.getActiveSpreadsheet();
  81.   var sheet = ss.getActiveSheet();
  82.   var rowsData = getRowsData_(sheet, getExportOptions(e));
  83.   var json = makeJSON_(rowsData, getExportOptions(e));
  84.   displayText_(json);
  85. }
  86.  
  87. function getExportOptions(e) {
  88.   var options = {};
  89.  
  90.   options.language = e && e.parameter.language || DEFAULT_LANGUAGE;
  91.   options.format   = e && e.parameter.format || DEFAULT_FORMAT;
  92.   options.structure = e && e.parameter.structure || DEFAULT_STRUCTURE;
  93.  
  94.   var cache = CacheService.getPublicCache();
  95.   cache.put('language', options.language);
  96.   cache.put('format',   options.format);
  97.   cache.put('structure',   options.structure);
  98.  
  99.   Logger.log(options);
  100.   return options;
  101. }
  102.  
  103. function makeJSON_(object, options) {
  104.   if (options.format == FORMAT_PRETTY) {
  105.     var jsonString = JSON.stringify(object, null, 4);
  106.   } else if (options.format == FORMAT_MULTILINE) {
  107.     var jsonString = Utilities.jsonStringify(object);
  108.     jsonString = jsonString.replace(/},/gi, '},\n');
  109.     jsonString = prettyJSON.replace(/":\[{"/gi, '":\n[{"');
  110.     jsonString = prettyJSON.replace(/}\],/gi, '}],\n');
  111.   } else {
  112.     var jsonString = Utilities.jsonStringify(object);
  113.   }
  114.   if (options.language == LANGUAGE_PYTHON) {
  115.     // add unicode markers
  116.     jsonString = jsonString.replace(/"([a-zA-Z]*)":\s+"/gi, '"$1": u"');
  117.   }
  118.   return jsonString;
  119. }
  120.  
  121. function displayText_(text) {
  122.   var output = HtmlService.createHtmlOutput("<textarea style='width:100%;' rows='20'>" + text + "</textarea>");
  123.   output.setWidth(400)
  124.   output.setHeight(300);
  125.   SpreadsheetApp.getUi()
  126.       .showModalDialog(output, 'Exported JSON');
  127. }
  128.  
  129. // getRowsData iterates row by row in the input range and returns an array of objects.
  130. // Each object contains all the data for a given row, indexed by its normalized column name.
  131. // Arguments:
  132. //   - sheet: the sheet object that contains the data to be processed
  133. //   - range: the exact range of cells where the data is stored
  134. //   - columnHeadersRowIndex: specifies the row number where the column names are stored.
  135. //       This argument is optional and it defaults to the row immediately above range;
  136. // Returns an Array of objects.
  137. function getRowsData_(sheet, options) {
  138.   var headersRange = sheet.getRange(1, 1, sheet.getFrozenRows(), sheet.getMaxColumns());
  139.   var headers = headersRange.getValues()[0];
  140.   var dataRange = sheet.getRange(sheet.getFrozenRows()+1, 1, sheet.getMaxRows(), sheet.getMaxColumns());
  141.   var objects = getObjects_(dataRange.getValues(), normalizeHeaders_(headers));
  142.   if (options.structure == STRUCTURE_HASH) {
  143.     var objectsById = {};
  144.     objects.forEach(function(object) {
  145.       objectsById[object.id] = object;
  146.     });
  147.     return objectsById;
  148.   } else {
  149.     return objects;
  150.   }
  151. }
  152.  
  153. // getColumnsData iterates column by column in the input range and returns an array of objects.
  154. // Each object contains all the data for a given column, indexed by its normalized row name.
  155. // Arguments:
  156. //   - sheet: the sheet object that contains the data to be processed
  157. //   - range: the exact range of cells where the data is stored
  158. //   - rowHeadersColumnIndex: specifies the column number where the row names are stored.
  159. //       This argument is optional and it defaults to the column immediately left of the range;
  160. // Returns an Array of objects.
  161. function getColumnsData_(sheet, range, rowHeadersColumnIndex) {
  162.   rowHeadersColumnIndex = rowHeadersColumnIndex || range.getColumnIndex() - 1;
  163.   var headersTmp = sheet.getRange(range.getRow(), rowHeadersColumnIndex, range.getNumRows(), 1).getValues();
  164.   var headers = normalizeHeaders_(arrayTranspose_(headersTmp)[0]);
  165.   return getObjects(arrayTranspose_(range.getValues()), headers);
  166. }
  167.  
  168.  
  169. // For every row of data in data, generates an object that contains the data. Names of
  170. // object fields are defined in keys.
  171. // Arguments:
  172. //   - data: JavaScript 2d array
  173. //   - keys: Array of Strings that define the property names for the objects to create
  174. function getObjects_(data, keys) {
  175.   var objects = [];
  176.   for (var i = 0; i < data.length; ++i) {
  177.     var object = {};
  178.     var hasData = false;
  179.     for (var j = 0; j < data[i].length; ++j) {
  180.       var cellData = data[i][j];
  181.       if (isCellEmpty_(cellData)) {
  182.         continue;
  183.       }
  184.       object[keys[j]] = cellData;
  185.       hasData = true;
  186.     }
  187.     if (hasData) {
  188.       objects.push(object);
  189.     }
  190.   }
  191.   return objects;
  192. }
  193.  
  194. // Returns an Array of normalized Strings.
  195. // Arguments:
  196. //   - headers: Array of Strings to normalize
  197. function normalizeHeaders_(headers) {
  198.   var keys = [];
  199.   for (var i = 0; i < headers.length; ++i) {
  200.     var key = normalizeHeader_(headers[i]);
  201.     if (key.length > 0) {
  202.       keys.push(key);
  203.     }
  204.   }
  205.   return keys;
  206. }
  207.  
  208. // Normalizes a string, by removing all alphanumeric characters and using mixed case
  209. // to separate words. The output will always start with a lower case letter.
  210. // This function is designed to produce JavaScript object property names.
  211. // Arguments:
  212. //   - header: string to normalize
  213. // Examples:
  214. //   "First Name" -> "firstName"
  215. //   "Market Cap (millions) -> "marketCapMillions
  216. //   "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored"
  217. function normalizeHeader_(header) {
  218.   var key = "";
  219.   var upperCase = false;
  220.   for (var i = 0; i < header.length; ++i) {
  221.     var letter = header[i];
  222.     if (letter == " " && key.length > 0) {
  223.       upperCase = true;
  224.       continue;
  225.     }
  226.     if (!isAlnum_(letter)) {
  227.       continue;
  228.     }
  229.     if (key.length == 0 && isDigit_(letter)) {
  230.       continue; // first character must be a letter
  231.     }
  232.     if (upperCase) {
  233.       upperCase = false;
  234.       key += letter.toUpperCase();
  235.     } else {
  236.       key += letter.toLowerCase();
  237.     }
  238.   }
  239.   return key;
  240. }
  241.  
  242. // Returns true if the cell where cellData was read from is empty.
  243. // Arguments:
  244. //   - cellData: string
  245. function isCellEmpty_(cellData) {
  246.   return typeof(cellData) == "string" && cellData == "";
  247. }
  248.  
  249. // Returns true if the character char is alphabetical, false otherwise.
  250. function isAlnum_(char) {
  251.   return char >= 'A' && char <= 'Z' ||
  252.     char >= 'a' && char <= 'z' ||
  253.     isDigit_(char);
  254. }
  255.  
  256. // Returns true if the character char is a digit, false otherwise.
  257. function isDigit_(char) {
  258.   return char >= '0' && char <= '9';
  259. }
  260.  
  261. // Given a JavaScript 2d Array, this function returns the transposed table.
  262. // Arguments:
  263. //   - data: JavaScript 2d Array
  264. // Returns a JavaScript 2d Array
  265. // Example: arrayTranspose([[1,2,3],[4,5,6]]) returns [[1,4],[2,5],[3,6]].
  266. function arrayTranspose_(data) {
  267.   if (data.length == 0 || data[0].length == 0) {
  268.     return null;
  269.   }
  270.  
  271.   var ret = [];
  272.   for (var i = 0; i < data[0].length; ++i) {
  273.     ret.push([]);
  274.   }
  275.  
  276.   for (var i = 0; i < data.length; ++i) {
  277.     for (var j = 0; j < data[i].length; ++j) {
  278.       ret[j][i] = data[i][j];
  279.     }
  280.   }
  281.  
  282.   return ret;
  283. }
Add Comment
Please, Sign In to add comment