I have a script that will import data from a csv file in to a Google sheet. I would like to include the following to remove the quote marks from the data - .replace(/"/g, "") - but am not sure of the best place/syntax to add in to the existing script.
Do I add it in to the section where I convert the csv data to an array -
function CSVToArray_(strData){
var rows = strData.split("\n");
//Logger.log(rows.length);
var array = [];
for(n=0;n<rows.length;++n){
if(rows[n].split(',').length>1){
array.push(rows[n].split(','));
}
}
Logger.log(array);
return array;
}
...or to the section where the sheet gets updated with the array data ?
var impSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B8:B8").getValue();
var csvData = CSVToArray_(csvFile);// convert to 2D array
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(impSheet);
sheet.clear();
sheet.getRange(1,1, csvData.length, csvData[0].length).setValues(csvData);// write to sheet in one single step
Any guidance/suggestions would be greatly appreciated.
The code above is dangerous and will break if there is a comma within an element even if it is in quotes which is valid csv. I suggest checking out this library https://code.google.com/p/jquery-csv/. Also, are you wanting to remove quotes or are you wanting to convert string values to numbers? That is, are you getting "2" and wanting it to be just 2, if so, you may just be wanting to parse it into a number as follows: parseInt("2").
If you don't care about the above then the following should work:
function CSVToArray_(strData){
var rows = strData.split("\n");
//Logger.log(rows.length);
var array = [];
var array_inner;
// iterate over rows
for (n=0; n<rows.length; ++n) {
array_inner = rows[n].split(',');
if (array_inner.length>1) {
// iterate over columns
for (m=0; m<array_inner.length; m++) {
array_inner[m] = array_inner[m].replace(/"/g, "");
}
array.push(array_inner);
}
}
Logger.log(array);
return array
}
Related
I am trying to split a data set with an ID and JSON string into a structured table.
The difficult part is I need it to be dynamic, the JSON string varies often and I want headings to be determined by the unique values in the input column at that time. I need the script to be able to create headings if the string changes without needed to recode the script.
We have about 150 different JSON strings we are hoping to use this script on, without recoding it for each one. Each string has lots of data points.
I have a script working but it splits them one by one, need to build something that will do bulk in one go, by looping through all outputs in B and creating a column for each unique field in all the strings, then populating them.
The script works if I paste the additional info straight in, however I am having trouble reading from the sheet
var inputsheet = SpreadsheetApp.getActive().getSheetByName("Input");
var outputsheet = SpreadsheetApp.getActive().getSheetByName("Current Output");
var additionalinfo = inputsheet.getRange(1,1).getValue()
Logger.log(additionalinfo)
var rows = [],
data;
for (i = 0; i < additionalinfo.length; i++) {
for (j in additionalinfo[i]) {
dataq = additionalinfo[i][j];
Logger.log(dataq);
rows.push([j, dataq]);
}
dataRange = outputsheet.getRange(1, 1, rows.length, 2);
dataRange.setValues(rows);
}
}
Here is a link to the sample data. Note that in Sample 1 & 2 there are different headings, we need the script to identify this and create headings for both
https://docs.google.com/spreadsheets/d/1BMiVuAgDbibLw6yUG3IZ9iw4MZTaVVegkw_k3ItQ4mU/edit#gid=0
Try this script that produces dynamic headers based on the json that has been read. It collects all json data, get its keys, and remove the duplicates.
Script:
function JSON_SPLITTER() {
var spreadsheet = SpreadsheetApp.getActive();
var inputsheet = spreadsheet .getSheetByName("Input");
var outputsheet = spreadsheet .getSheetByName("Current Output");
var additionalinfo = inputsheet.getDataRange().getValues();
var keys = [];
// prepare the additionalInfo data to be parsed for later
var data = additionalinfo.slice(1).map(row => {
// collect all keys in an array
if (JSON.parse(row[1]).additionalInfo) {
keys.push(Object.keys(JSON.parse(row[1]).additionalInfo));
return JSON.parse(row[1]).additionalInfo;
}
else {
keys.push(Object.keys(JSON.parse(row[1])));
return JSON.parse(row[1]);
}
});
// unique values of keys, modified to form header
var headers = [...new Set(keys.flat())]
// Add A1 as the header for the ids
headers.unshift(additionalinfo[0][0]);
// set A1 and keys as headers
var output = [headers]
// build output array
additionalinfo.slice(1).forEach((row, index) => {
var outputRow = [];
headers.forEach(column => {
if(column == 'Contract Oid')
outputRow.push(row[0]);
else
outputRow.push(data[index][column]);
});
output.push(outputRow)
});
outputsheet.getRange(1, 1, output.length, output[0].length).setValues(output);
}
Output:
Update:
Modified script for no-additionalInfo key objects.
I'm trying to automate hyperlink creations on my GSheet.
Here's my script:
function ticketURLGenerator() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Data");
var range = sheet.getRange("C2:C");
var ticketID = range.getValue();
Logger.log(ticketID);
for(i = 0; i < ticketID.length; i++){
if(ticketID.length === 0){
ticketID.setValue('')
} else if(ticketID.length > 4){
ticketID.setValue('=HYPERLINK' + '("https://mylink.com/'+ticketID+'";'+ticketID+')');
}
}
}
It does nothing but when I change ticketID.setValue by sheet.getRange("C2:C").setValue it put the whole range in the url. We can see with Logger.log(ticketID) that the whole range is selected.
So according to this result, i'm missing how to get the value of each cell individualy in the range and then check if they are long enought to create an individual url. Do I need to use something like range[i] somewhere? I'm lost.
I believe your goal as follows.
You want to retrieve the values from the cells "C2:C".
When the length of value is more than 4, you want to create a formula of HYPERLINK.
When the length of value is less than 4, you don't want to put the formula.
You want to put the formulas to the cells "C2:C".
Modification points:
When range of var range = sheet.getRange("C2:C") is used, the value of var ticketID = range.getValue() is the value of cell "C2". When you want to retrieve values from the cells "C2:C", please use getValues instead of getValue.
In this case, the retrieved value is 2 dimensional array.
When range.getValue() is the string value, ticketID of var ticketID = range.getValue() is also the string. So I think that when ticketID.setValue('##') is run, an error occurs.
In your script, setValue is used in a loop. In this case, the process cost will become high.
And, when sheet.getRange("C2:C" + sheet.getLastRow()) is used instead of sheet.getRange("C2:C"), the process cost will become low a little.
When above points are reflected to your script, it becomes as follows.
Modified script:
function ticketURLGenerator() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Data");
var range = sheet.getRange("C2:C" + sheet.getLastRow());
var ticketIDs = range.getValues();
var values = ticketIDs.map(([c]) => [c.toString().length > 4 ? `=HYPERLINK("https://mylink.com/${c}";"${c}")` : c]);
range.setValues(values);
}
In this modification, the values are retrieved from the cells of "C2:C" + sheet.getLastRow(), and an array including the formulas and values is created, and then, the array is put to the cells.
And I used the template literal for creating the formula.
Note:
In this case, please use this script with enabling V8 runtime.
References:
getLastRow()
getValues()
map()
Template literals
You just need to apply the HYPERLINK operation to the tickets that their length is more than 4. To achieve that, you can use map() to iterate over all the elements in your list.
Solution:
function ticketURLGenerator() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Data");
const ticketR = sheet.getRange("C2:C"+sheet.getLastRow());
const ticketIDs = ticketR.getDisplayValues().flat();
const hLinks = ticketIDs.map(ti=>{
if(ti.length>4) {
return [`=HYPERLINK("https://mylink.com/${ti}"; ${ti})`]}
else {return [ti]}
})
ticketR.setValues(hLinks);
}
I'm looking for a way to take 2 columns in a google spreadsheet and merge them into a single array in hopes that I can take these 2 columns and use setValues on a new sheet.
Why?
I'm eventually taking 2 different sheets and basically doing a large scale vlookup and transferring all results and desired columns into a single, new sheet. I can get the full dataRange, loop through each array, grabbing the values I want and pushing them to a new array. But is there an easier way? If I can look through just row1 and get the headers and their index, can I just put all of column A and column D in a multi-dimensional array?
Example
Header1 | H2 | H3
I want H1 and H3 and their rows so I can put them in a new sheet as such
Multi-Dimensional Array:
[ [H1, H3], [dataH1,dataH3] ]
Current Code
var freqArr = new Array(); //Array with sheet data
var myArray = new Array(); //Blank array to house header index
var freqSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('KEY_test_test');
var freqData = freqSheet.getDataRange(); //all data
var freqNumRows = freqData.getNumRows(); //number of rows
var freqNumCol = freqData.getNumColumns(); //number of columns
freqArr = freqSheet.getRange(1, 1, freqNumRows, freqNumCol).getValues();
for (i = 0;i<1;++i){
for (j = 0;j<freqNumCol;++j){
if (freqArr[i][j].toString() == 'Header1' || freqArr[i][j].toString() == 'Header3'){
myArray.push([j]);
}
}
}
Logger.log(myArray);
Where I'm Stuck
What I'm doing right now is looping through the first row to get the header indexes I want (should look like this [ 0, 2 ]) but all that is returning in my log is []. I plan to use this array of indexes to loop through my freqData and grab the indexes of each nested array.
Any advice would be great. I'm just starting to learn google script and I'm teaching myself. Thanks
UPDATE TO CODE:
It turns out that .toString() == 'Header1' will not return a match but after more google fu, I found .toString().match('Header1') == 'Header1' will return what I need. See below for update
var freqArr = new Array(); //Array with sheet data
var myArray = new Array(); //Blank array to house header index
var freqSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('KEY_test_test');
var freqData = freqSheet.getDataRange(); //all data
var freqNumRows = freqData.getNumRows(); //number of rows
var freqNumCol = freqData.getNumColumns(); //number of columns
freqArr = freqSheet.getRange(1, 1, freqNumRows, freqNumCol).getValues();
for (i = 0;i<1;++i){
for (j = 0;j<freqNumCol;++j){
if (freqArr[i][j].toString().match('Header1') == 'Header1' || freqArr[i][j].toString().match('Header3') == 'Header3'){
myArray.push(j);
}
}
}
Logger.log(myArray);
will return [ 0.0 , 2.0 ].
But still, my question remains, is there a faster way to get 2(n) columns that are not side-by-side and put them into an array so that you can use .setValues?
Answer
But still, my question remains, is there a faster way to get 2(n) columns that are not side-by-side and put them into an array so that you can use .setValues?
Yes, there are many ways. One of them is the use of a JavaScript method: array.prototype.forEach()
Code
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var array = [];
data.forEach(function(row){
array.push([row[0],row[5]]);
});
sheet.getRange(1,10,array.length,2).setValues(array);
}
Explanation
Get the active sheet
var sheet = SpreadsheetApp.getActiveSheet();
Get the all the values on sheet
var data = sheet.getDataRange().getValues();
Initialize a variable to hold the array
var array = [];
Get the values of the first and sixth columns (A and F) (zero based index)
data.forEach(function(row){
array.push([row[0],row[5]]);
});
Return the values to a range starting on J1 and ending on column K and the required row (one based index)
sheet.getRange(1,10,array.length,2).setValues(array);
Take a look at the getRowsData() function on the Simple Mail Merge tutorial. It will get all the data in a sheet and return it as objects. You could then access the data as myData[i].header1 It will remove spaces and "normalize" the header. So a header such as My Header name will be myData[i].myHEaderName
You could limit the returned data to only the columns you need if you wish.
I am trying to parse a CSV file into a Javascript array but have run into a an issue which I am a little stumped.
Though the rest of the objects are parsed without a quote for the key, one of the Keys is in quotes but when I try Object[key] I get an exception
Uncaught SyntaxError: Invalid or unexpected token
I am able to read all other keys except for "information".
My object looks like this:
Object {LGA_NAME: "DANDENONG", Lat: "-37.98862", Long: "145.21805", "Information
": "something crashed
"}
The CSV file in question is
https://dl.dropboxusercontent.com/u/97162408/crashdata.csv
The function which I use to parse the CSV file is
function csvToArray(csvString) {
// The array we're going to build
var csvArray = [];
// Break it into rows to start
var csvRows = csvString.split(/\n/);
// Take off the first line to get the headers, then split that into an array
var csvHeaders = csvRows.shift().split(',');
// Loop through remaining rows
for (var rowIndex = 0; rowIndex < csvRows.length; ++rowIndex) {
var rowArray = csvRows[rowIndex].split(',');
// Create a new row object to store our data.
var rowObject = csvArray[rowIndex] = {};
// Then iterate through the remaining properties and use the headers as keys
for (var propIndex = 0; propIndex < rowArray.length; ++propIndex) {
// Grab the value from the row array we're looping through...
var propValue = rowArray[propIndex];
// ...also grab the relevant header (the RegExp in both of these removes quotes)
var propLabel = csvHeaders[propIndex];
rowObject[propLabel] = propValue;
}
}
return csvArray;
}
The problem is in the way you split the string to get the lines. In *NIX systems, lines are broken with \n, but in Windows lines are broken with \r\n. Because the CSV file is following the Windows convention and you split the lines using just \n, the last key is actually "Information\r".
You can fix this issue by replacing var csvRows = csvString.split(/\n/); with var csvRows = csvString.split(/\n|\r\n/); this way you are going to be able to parse CSV files that use either line break conventions.
I am trying to get this function to run on a spreadsheet and find and replace all apostrophes with nothing. Right now it works but it takes too long, right now in my spreadsheet I have 12 rows and it took 128 seconds to run on just those. What I want to try and make this do is only activate on the last row in the spreadsheet.
I tried getting it to work by adding var row = r.getLastRow(); and changing a few spots to use row. I was not able to get it to run when I did that. I am going to have it run every time a form is submitted so it should always be the last row.
I got the code from: https://productforums.google.com/d/msg/docs/7IlOotksJ4I/liXa0SrC-R4J
function fandr() {
var r=SpreadsheetApp.getActiveSheet().getDataRange();
var rws=r.getNumRows();
var cls=r.getNumColumns();
var i,j,a,find,repl;
find="'";
repl="";
for (i=1;i<=rws;i++) {
for (j=1;j<=cls;j++) {
a=r.getCell(i, j).getValue();
if (r.getCell(i,j).getFormula()) {continue;}
try {
a=a.replace(find,repl);
r.getCell(i, j).setValue(a);
}
catch (err) {continue;}
}
}
}
In my opinion, best way to replace text with script is to use map. This function was suggested by #serge-insas here. You could modify it to get best performance results and replace only last row values:
function testReplaceInRange(){
var sheet = SpreadsheetApp.getActiveSheet()
var lastRow = sheet.getLastRow();
var DataRange = sheet.getDataRange();
var range = DataRange.offset(lastRow - 1, 0, 1); // last Data row
replaceInRange(range,"'","");
}
function replaceInRange(range, to_replace, replace_with) {
//get the current data range values as an array
var values = range.getValues();
// make RegExp
var Rep = new RegExp(to_replace, 'g');
//loop over the rows in the array
for(var row in values){
//use Array.map to execute a replace call on each of the cells in the row.
var replaced_values = values[row].map(function(original_value){
return original_value.toString().replace(Rep,replace_with);
});
//replace the original row values with the replaced values
values[row] = replaced_values;
}
//write the updated values to the range
range.setValues(values);
}
I've also used the trich with regular expressions, suggested by #cory-gross here. It's made in purpose to raplace all occurrences of to_replace text, not only the first one.
I expirienced the same performance issue and found that Google encourages getValues over getValue when handling many cells. You get a 2-dimensional array with getValues. See https://developers.google.com/apps-script/reference/spreadsheet/range#getvalues