Append multiple row instead of single row from json - json

I have script to copy json data to google sheet, bus Is there any way to append multiple rows instead of single row
Can anybody explain how i change this script ? Thanks
function doPost(request = {}) {
const { parameter, postData: { contents, type } = {} } = request; //request data
const { dataReq = {} } = JSON.parse(contents); //content
const { fname = {} } = JSON.parse(contents); //function name
const response = {
status: "function not found: " + fname, // prepare response in function not found
data2: dataReq
}
switch (fname) { //function selection
case 'pasteData':
var output = JSON.stringify(pasteDAta(dataReq)) //call function with data from request
break
default:
var output = JSON.stringify(response)
break
}
return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.JSON); //response to frontend
}
function pasteDAta(dataReq) {
const id = '1_27rjNQmlXrwVKpLWUbGrJYPJufGRa7Dk-XEKcNAHr0'; //id of Google Sheet
var sheet = SpreadsheetApp.openById(id).getSheetByName('Sheet1'); //sheet
var headings = sheet.getDataRange().getValues()[0]; //Headers
var i = 0 //to test the times that efectively adds rows the forEach function
dataReq.forEach((a) => { //go trought every item on dataReq as 'a'
let holder = []; //to steore temp the elements
for (x in headings) { //to add in order of Headers on sheet
let output = (headings[x] in a) ? a[headings[x]] : ''; //if exist add, if not empty
holder.push(output); //add to holder
}
sheet.appendRow(holder); //put holder(order data) on sheet
i += 1 //to test the times
});
return "Numbers of sheets added: "+i;
}

In your script, how about the following modification?
From:
var i = 0 //to test the times that efectively adds rows the forEach function
dataReq.forEach((a) => { //go trought every item on dataReq as 'a'
let holder = []; //to steore temp the elements
for (x in headings) { //to add in order of Headers on sheet
let output = (headings[x] in a) ? a[headings[x]] : ''; //if exist add, if not empty
holder.push(output); //add to holder
}
sheet.appendRow(holder); //put holder(order data) on sheet
i += 1 //to test the times
});
return "Numbers of sheets added: "+i;
To:
var values = dataReq.map((a) => {
let holder = [];
for (x in headings) {
let output = (headings[x] in a) ? a[headings[x]] : '';
holder.push(output);
}
return holder;
});
var len = values.length;
sheet.getRange(sheet.getLastRow() + 1, 1, len, values[0].length).setValues(values);
return "Numbers of sheets added: " + len;
In this modification, map is used instead of forEach. And, a 2-dimensional array is returned. This array is appended to the sheet using setValues.
Note:
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful this.
You can see the detail of this in the report of "Redeploying Web Apps without Changing URL of Web Apps for new IDE".
If this modification was not the direct solution to your issue, when you provide the sample value of dataReq, I think that it will help to think of the modification points.
References:
map()
setValues(values)

Related

Split array values based on cumulative values in another column + Google app script

I'm trying to find way with Google App Script to split column A of this spreadsheet when we reach cumulative value = 50 in column B. If the cumulative is not exact match, we need to stop each split at the last row in which we have cumulative value lower than 50.
I added in column C the expected splitted arrays result.
Here is the sample spreadsheet : https://docs.google.com/spreadsheets/d/1_8ZRTxd64qbxCHrhwDoo4ugWHy7jG1VIKv8hHjtp3Bw/edit#gid=0
The final goal would be to store the values of each result Array in a text file, and upload in a Drive folder.
Thanks in advance for your help,
expected Array1 as example in a txt file
==========================================
Edit, from #Tanaike scripts, I updated the script like the following with no luck:
function test2() {
var raw_values = SpreadsheetApp.getActive().getSheetByName("Sheet1").getRange("A2:J").getValues();
var values = raw_values.map((x) => [x[0], x[2], x[4]])
var destFolderID = "1Qq52QRpYYG_T2AxNWDz0rykZbAGhdpoe";
var fileName = "sample";
createTsv_new(values, destFolderID, fileName)
}
function createTsv_new(values, destFolderID, fileName) {
const folderId = destFolderID; // Please set the folder ID you want to put the created files.
const { res } = values.reduce((o, [s, a, b], i, v) => {
o.tempC += b;
o.tempAr.push([s, a]);
if (o.tempC + (v[i + 1] ? v[i + 1][4] : 0) > 50 || i == v.length - 1) {
o.res.push(o.tempAr);
o.tempAr = [];
o.tempC = 0;
}
return o;
}, { res: [], tempAr: [], tempC: 0 });
if (res.length == 0) return;
res.forEach((e, i) => DriveApp.getFolderById(folderId).createFile(fileName + (i + 1) + ".tsv", e.join("\t")));
}
For now, the expected splitted file result is the following : updated expected file result
In your situation, how about the following sample script?
Sample script:
function myFunction1() {
const folderId = "root"; // Please set the folder ID you want to put the created files.
// 1. Retrieve values from Spreadsheet.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
const range = sheet.getRange("A1:B" + sheet.getLastRow());
const [header, ...values] = range.getValues();
// 2. Create an array including the separated rows.
const { res } = values.reduce((o, [a, b], i, v) => {
o.tempC += b;
o.tempAr.push(a);
// Here, the rows are separated.
if (o.tempC + (v[i + 1] ? v[i + 1][1] : 0) > 50 || i == v.length - 1) {
o.res.push(o.tempAr);
o.tempAr = [];
o.tempC = 0;
}
return o;
}, { res: [], tempAr: [], tempC: 0 });
// 3. Create text files using the created array.
if (res.length == 0) return;
res.forEach((e, i) => DriveApp.getFolderById(folderId).createFile(`Array${i + 1}.txt`, [header[0], ...e].join("\n")));
}
When this script is run for your provided Spreadsheet, the values are retrieved from the columns "A" and "B". And, create an array including the separated rows. And, using the array, the text files are created.
From your showing image, the filenames are like Array1.txt, Array2.txt,,,.
From your showing image, the header row is put to each text files. If you don't want to include the header, please modify [header[0], ...e].join("\n") to e.join("\n").
Note:
This sample script is for your provided Spreadsheet. So, when you change the Spreadsheet, this script might not be able to be used. Please be careful about this.
References:
reduce()
map()
createFile(name, content)
Added:
About your following second new question,
I change a bit the disposition of columns in the spreadsheet : docs.google.com/spreadsheets/d/…. I would like to include column A and column C in each final file, and the cumul is now based on column E. The final file would be a .TSV file.
The sample script is as follows.
Sample script:
function myFunction2() {
const folderId = "root"; // Please set the folder ID you want to put the created files.
// 1. Retrieve values from Spreadsheet.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
const range = sheet.getRange("A1:E" + sheet.getLastRow());
const [header, ...values] = range.getValues();
// 2. Create an array including the separated rows.
const { res } = values.reduce((o, [a, , c, , e], i, v) => {
o.tempC += e;
o.tempAr.push([a, c].join("\t"));
// Here, the rows are separated.
if (o.tempC + (v[i + 1] ? v[i + 1][4] : 0) > 50 || i == v.length - 1) {
o.res.push(o.tempAr);
o.tempAr = [];
o.tempC = 0;
}
return o;
}, { res: [], tempAr: [], tempC: 0 });
// 3. Create text files using the created array.
if (res.length == 0) return;
res.forEach((e, i) => DriveApp.getFolderById(folderId).createFile(`Array${i + 1}.txt`, [[header[0], header[2]].join("\t"), ...e].join("\n")));
}

Is there a way to list the array values in one cell by adding one onto another

I'm making a google sheets app function that checks if the ID in one sheet can be associated with any of the patients (each patient receives an ID), then add it into their file (a single cell next to their name).
I'm at a point where I can get the info into the cell with .copyValuesToRange, but the problem is that all the values are copied into the cell one after another. The desired effect is that I get all values separated by ", ".
Here's my code:
function newCaseIn() {
let app = SpreadsheetApp;
let dest = app.getActiveSpreadsheet().getSheetByName("Baza Danych");
let form = app.getActiveSpreadsheet().getSheetByName("Zgloszenia");
for (let i = 2; i < 200; i++) {
if (form.getRange(i, 2).getValue()) {
while (true) {
form.getRange(i, 3).copyValuesToRange(0, 9, 9, 2, 2);
}
}
}
}
And here's how the database looks: Database FormSubmissions
NOTE: There is a form that comes down to the second sheet to allow people submit new patient files to a specified ID
It could be something like this:
function main() {
let ss = SpreadsheetApp.getActiveSpreadsheet();
let dest = ss.getSheetByName("Baza Danych");
let form = ss.getSheetByName("Zgloszenia");
// get all data from the form
var source_data = form.getDataRange().getValues();
source_data.shift(); // remove the header
// make the data object
// in: 2d array [[date,id1,doc], [date,id2,doc], ...]
// out: object {id1: [doc, doc, doc], id2: [doc, doc], ...}
var source_obj = {};
while(source_data.length) {
let [date, id, doc] = source_data.shift();
try { source_obj[id].push(doc) } catch(e) { source_obj[id] = [doc] }
}
// get all data from the dest sheet
var dest_data = dest.getDataRange().getValues();
// make a new table from the dest data and the object
var table = [];
while (dest_data.length) {
let row = dest_data.shift();
let id = row[0];
let docs = source_obj[id]; // get docs from the object
if (docs) row[8] = docs.join(', ');
table.push(row);
}
// clear the dest sheet and put the new table
dest.clearContents();
dest.getRange(1,1,table.length,table[0].length).setValues(table);
}
Update
The code from above clears existed docs in the cells of column 9 and fills it with docs from the form sheet (for relevant IDs).
If the dest sheet already has some docs in the column 9 and you want to add new docs you have to change the last loop this way:
// make a new table from the dest data and the object
var table = [];
while (dest_data.length) {
let row = dest_data.shift();
let id = row[0];
let docs = source_obj[id]; // get docs from the object
if (docs) {
let old_docs = row[8];
row[8] = docs.join(', ');
if (old_docs != '') row[8] = old_docs + ', ' + row[8];
}
table.push(row);
}

How to return google sheet values using doPost using x-www-form-urlencoded?

I try to use google sheets to write and read some data using post requests,
the writing part works, but it never returns any value back.
function doPost(e) { return handleResponse(e); }
function handleResponse(e) {
// Get public lock, one that locks for all invocations
// (https://gsuite-developers.googleblog.com/2011/10/concurrency-and-google-apps-script.html)
var lock = LockService.getPublicLock();
// Allow the write process up to 2 seconds
lock.waitLock(2000);
try {
// Generate a (not very good) UUID for this submission
var submissionID = e.parameter.id || 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
// Open the spreadsheet document and select the right sheet page
var sheetName = e.parameter.sheet_name|| 'Sheet1';
var doc = SpreadsheetApp.openById(SCRIPT_PROP.getProperty("key"));
var sheet = doc.getSheetByName(sheetName);
//get information out of post request
var action = e.parameter.action || 'save';
var pName = e.parameter.name;
var rowNumber = findRow(pName,sheetName);
var headRow = e.parameter.header_row || 1;
var headers = sheet.getRange(headRow, 1, 1, sheet.getLastColumn()).getValues()[0];
// check for action is loading
if(action == 'load'){
//check if the name has data
if (rowNumber){
//loads all the give values out of the parameters
var answer = [];
Logger.log('hadders: ' + headers);
for (i in headers) {
if (e.parameter[headers[i].toLowerCase()] !== undefined) {
var val = sheet.getRange(rowNumber, 1, 1,sheet.getLastColumn()).getValues()[0][i];
answer.push(val);
}
}
Logger.log('answer: '+ answer);
// Return result in JSON
return ContentService
.createTextOutput({body:{parameter:{answer}}})
.setMimeType(ContentService.MimeType.JSON)
;
}else{
// return error name wasn't found in sheet.
return ContentService
.createTextOutput("can't find Name")
.setMimeType(ContentService.MimeType.TEXT)
;
}
}
The logger returns all the right values,
but logging the return value from this function ends up in an empty object.
I tried just making my own return object like:
return ContentService
.createTextOutput({body={parameter={answer=JSON.stringify(answer)}}})
.setMimeType(ContentService.MimeType.TEXT)
;
I know that I need to use &= instead of ,: but it still returned nothing.
In your script, how about modifying as follows?
From:
return ContentService
.createTextOutput({body:{parameter:{answer}}})
.setMimeType(ContentService.MimeType.JSON)
To:
return ContentService
.createTextOutput(JSON.stringify({body:{parameter:{answer}}}))
.setMimeType(ContentService.MimeType.JSON)
In the case of createTextOutput({body:{parameter:{answer}}}), the object cannot be directly put. So I thought that it is required to convert it to the string.
Note:
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful this.
You can see the detail of this in the report of "Redeploying Web Apps without Changing URL of Web Apps for new IDE".

Google form: create two rows with different information

Im trying to create a google form with 2 fields, one for "item" and the other for "quantity"
Since user might need to send miltiple items I want to create 1 form only and sort the information.
my Google form
So far I have managed to add a script that splits the information submitted in "item" into many rows, however, Im not able to do the same with the field "quantity"
I got this information from this post
This is my script:
function onFormSubmit(e) {
var ss = SpreadsheetApp.openByUrl("URL_here");
var sheet = ss.getSheetByName("FormResponses");
// Form Response retrieved from the event object
const formResponse = e.response;
var itemResponses = formResponse.getItemResponses();
// Add responses comma-separated included
var rowData = itemResponses.map(item => item.getResponse().toString());
rowData.splice(0, 0, formResponse.getTimestamp());
// Split into different rows afterwards
if (rowData[1].includes(',')) {
rowData[1].split(',').forEach(instanceName => {
let tmpRow = rowData.map(data => data);
tmpRow[1] = instanceName;
sheet.appendRow(tmpRow);
// Append to the sheet
});
}
else {
sheet.appendRow(rowData); // Append to the sheet
}
Current results:
Click here to see image
What I want to get:
Click here to see image
Thanks
When I saw your script, only the 2nd element of rowData is split with ,. I thought that this might be the reason for your issue. And, when appendRow is used in a loop, the process cost will become high. So, in your situation, how about the following modification?
From:
rowData.splice(0, 0, formResponse.getTimestamp());
// Split into different rows afterwards
if (rowData[1].includes(',')) {
rowData[1].split(',').forEach(instanceName => {
let tmpRow = rowData.map(data => data);
tmpRow[1] = instanceName;
sheet.appendRow(tmpRow);
// Append to the sheet
});
}
else {
sheet.appendRow(rowData); // Append to the sheet
}
To:
var date = formResponse.getTimestamp();
var values = rowData.map(v => v.includes(',') ? v.split(",") : [v]);
var res = values[0].map((_, c) => [date, ...values.map(r => r[c] || "")]);
sheet.getRange(sheet.getLastRow() + 1, 1, res.length, res[0].length).setValues(res);
Reference:
map()

Google Sheets Scraping Options Chain from Yahoo Finance, Incomplete Results [duplicate]

This question already has answers here:
Scraping data to Google Sheets from a website that uses JavaScript
(2 answers)
Closed last month.
I'm attempting to scrape options pricing data from Yahoo Finance in Google Sheets. Although I'm able to pull the options chain just fine, i.e.
=IMPORTHTML("https://finance.yahoo.com/quote/TCOM/options?date=1610668800","table",2)
I find that it's returning results that don't completely match what's actually shown on Yahoo Finance. Specifically, the scraped results are incomplete - they're missing some strikes. i.e. the first 5 rows of the chart may match, but then it will start returning only every other strike (aka skipping every other strike).
Why would IMPORTHTML be returning "abbreviated" results, which don't match what's actually shown on the page? And more importantly, is there some way to scrape complete data (i.e. that doesn't skip some portion of the available strikes)?
In Yahoo finance, all data are available in a big json called root.App.main. So to get the complete set of data, proceed as following
var source = UrlFetchApp.fetch(url).getContentText()
var jsonString = source.match(/(?<=root.App.main = ).*(?=}}}})/g) + '}}}}'
var data = JSON.parse(jsonString)
You can then choose to fetch the informations you need. Take a copy of this example https://docs.google.com/spreadsheets/d/1sTA71PhpxI_QdGKXVAtb0Rc3cmvPLgzvXKXXTmiec7k/copy
edit
if you want to get a full list of available data, you can retrieve it by this simple script
// mike.steelson
let result = [];
function getAllDataJSON(url = 'https://finance.yahoo.com/quote/TCOM/options?date=1610668800') {
var source = UrlFetchApp.fetch(url).getContentText()
var jsonString = source.match(/(?<=root.App.main = ).*(?=}}}})/g) + '}}}}'
var data = JSON.parse(jsonString)
getAllData(eval(data),'data')
var sh = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
sh.getRange(1, 1, result.length, result[0].length).setValues(result);
}
function getAllData(obj,id) {
const regex = new RegExp('[^0-9]+');
for (let p in obj) {
var newid = (regex.test(p)) ? id + '["' + p + '"]' : id + '[' + p + ']';
if (obj[p]!=null){
if (typeof obj[p] != 'object' && typeof obj[p] != 'function'){
result.push([newid, obj[p]]);
}
if (typeof obj[p] == 'object') {
getAllData(obj[p], newid );
}
}
}
}
Here's a simpler way to get the last market price of a given option. Add this function to you Google Sheets Script Editor.
function OPTION(ticker) {
var ticker = ticker+"";
var URL = "finance.yahoo.com/quote/"+ticker;
var html = UrlFetchApp.fetch(URL).getContentText();
var count = (html.match(/regularMarketPrice/g) || []).length;
var query = "regularMarketPrice";
var loc = 0;
var n = parseInt(count)-2;
for(i = 0; i<n; i++) {
loc = html.indexOf(query,loc+1);
}
var value = html.substring(loc+query.length+9, html.indexOf(",", loc+query.length+9));
return value*100;
}
In your google sheets input the Yahoo Finance option ticker like below
=OPTION("AAPL210430C00060000")
I believe your goal as follows.
You want to retrieve the complete table from the URL of https://finance.yahoo.com/quote/TCOM/options?date=1610668800, and want to put it to the Spreadsheet.
Issue and workaround:
I could replicate your issue. When I saw the HTML data, unfortunately, I couldn't find the difference of HTML between the showing rows and the not showing rows. And also, I could confirm that the complete table is included in the HTML data. By the way, when I tested it using =IMPORTXML(A1,"//section[2]//tr"), the same result of IMPORTHTML occurs. So I thought that in this case, IMPORTHTML and IMPORTXML might not be able to retrieve the complete table.
So, in this answer, as a workaround, I would like to propose to put the complete table parsed using Sheets API. In this case, Google Apps Script is used. By this, I could confirm that the complete table can be retrieved by parsing the HTML table with Sheet API.
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet, and please enable Sheets API at Advanced Google services. And, please run the function of myFunction at the script editor. By this, the retrieved table is put to the sheet of sheetName.
function myFunction() {
// Please set the following variables.
const url ="https://finance.yahoo.com/quote/TCOM/options?date=1610668800";
const sheetName = "Sheet1"; // Please set the destination sheet name.
const sessionNumber = 2; // Please set the number of session. In this case, the table of 2nd session is retrieved.
const html = UrlFetchApp.fetch(url).getContentText();
const section = [...html.matchAll(/<section[\s\S\w]+?<\/section>/g)];
if (section.length >= sessionNumber) {
if (section[sessionNumber].length == 1) {
const table = section[sessionNumber][0].match(/<table[\s\S\w]+?<\/table>/);
if (table) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const body = {requests: [{pasteData: {html: true, data: table[0], coordinate: {sheetId: ss.getSheetByName(sheetName).getSheetId()}}}]};
Sheets.Spreadsheets.batchUpdate(body, ss.getId());
}
} else {
throw new Error("No table.");
}
} else {
throw new Error("No table.");
}
}
const sessionNumber = 2; means that 2 of =IMPORTHTML("https://finance.yahoo.com/quote/TCOM/options?date=1610668800","table",2).
References:
Method: spreadsheets.batchUpdate
PasteDataRequest