I have thousands of rows of data in a Google Sheets File in a column that looks something like
[{"amountMax":49.99,"amountMin":49.99,"availability":"true","color":"Brown","currency":"USD","dateSeen":["2019-04-11T08:00:00Z"],"isSale":"false","offer":"Online only","sourceURLs":["https://www.walmart.com/ip/SadoTech-Model-CXR-Wireless-Doorbell-1-Remote-Button-2-Plugin-Receivers-Operating-500-feet-Range-50-Chimes-Batteries-Required-Receivers-Beige-Fixed-C/463989633"]}]
I would like to be able to return the max value, the currency, the color attributes. How can I do that in Google Sheets. Ideally would like to do something like being able to retrieve the data attributes how I would normally in javascript like in this link here https://repl.it/#alexhoy/WetSlateblueDribbleware
However this does not seem to work for me when creating a function in script.google.com
For example, here is a slugify function which takes an input (cell) and turns it into a slug/handle without the need for looping. In Google Sheets I can then call =slugify(b2) and turn that value into slug form
/**
* Converts value to slug
* #customfunction
*/
function slugify(value) {
/*
* Convert the the vs in a range of cells into slugs.
* #customfunction
*/
let slug = '';
slug = value.substring(0, 100).toLowerCase();
slug = slug.replace(/[^\w\s-]/g, '');
slug = slug.replace(/\s+/g, '-');
Logger.log(slug);
return slug;
}
I want to do the same thing without looping to parse the object data above or declaring a range of values and what not.
Any suggestions on how I can do this in a simple way like shown above without the need for declaring active spreadsheet, range values and looping.
The following script will give you an idea about how to approach this task.
It assumes that:
the json data described in your question is in Cell A2.
the max value will be inserted into cell D2
the currency will be inserted into cell E2
the color will be inserted into cell F2
The script uses temporary arrays to capture the values and then assign it to a 2d array.
If you have many rows of data, then you will need to create a loop. I suggest that you build the arraydata progressively, and only update the target range at the end of the loop. This will give you the most efficient outcome.
function so6031098604() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet()
var content = JSON.parse(sheet.getRange("A2").getValue());
// temp arrar to capture the data
var temparray = [];
temparray.push(content[0]["amountMax"]);
temparray.push(content[0]["currency"]);
temparray.push(content[0]["color"]);
// second array to accept the row data
var arraydata =[];
arraydata.push(temparray)
// define the target range
var targetrange = sheet.getRange(2, 4, 1, 3);
// update with the arraydata
targetrange.setValues(arraydata);
}
You want a custom function that will return certain fields from a JSON array.
In the following example, the target cell can be a single cell or an array.
This example does not use an arrayformula. The mechanics of using an arrayformula with a custom function may be something that you can research here Custom SHEETNAME function not working in Arrayformula.
Note: A 30 second quota applies to the execution of a Custom function
/**
* gets the MaxAmount, Current and Color from the data
*
* #param {cell reference or range} range The range to analyse.
* #return amountMax,currency and color
* #customfunction
*/
function getJsonData(range) {
//so6031098606
// Test whether range is an array.
if (range.map) {
// if yes, then loop through the rows and build the row values
var jsonLine = [];
for (var i = 0; i < range.length; i++) {
var jsonValues=[];
var v = JSON.parse(range[i][0]);
jsonValues.push(v.amountMax);
jsonValues.push(v.currency);
jsonValues.push(v.color);
// aggregate the row values
jsonLine.push(jsonValues);
} // end i
return jsonLine;
} else {
// if no, then just return a single set of values
var v = JSON.parse(range);
var jsonValues = [];
jsonValues.push(v.amountMax);
jsonValues.push(v.currency);
jsonValues.push(v.color);
return [jsonValues];
}
}
Related
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);
}
Firstly, I am very new here. Searched the whole internet about this and finally landing a question here.
function StockCoverW(stock,range){
var x = stock;
var r = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
var r1 = SpreadsheetApp.getActiveSheet().getNamedRanges(range);
}
Here, stock is a single cell for example: A1
and range is a range of cell for example: A2:E2
so the function call will be StockCoverW(A1,A2:E2)
now I need to take the values from the range cells into an Array.
Obviously the code here is not correct as cannot debug or do anything.
How to implement this in google spreadsheet script?
Change getDataRange (which grabs all valued on sheet) to getRange(range)
function StockCoverW(stock,range){
var x = stock;
var r = SpreadsheetApp.getActiveSheet().getRange(range).getValues();
var r1 = SpreadsheetApp.getActiveSheet().getNamedRanges(range);
}
Note though that the function doesn’t return or alter anything so when it runs it just gets the valued and the named ranges.
Also note that the array you get with values is a 2d array and if you want it to be just a simple array then use
function StockCoverW(stock,range){
var x = stock;
var r = SpreadsheetApp.getActiveSheet().getRange(range).getValues()[0];
var r1 = SpreadsheetApp.getActiveSheet().getNamedRanges(range);
}
I'm trying to retrieve a specific value from a google app script sheet. I have a sheet where it stores information. I want to loop through that sheet based on row and retrieve all values that match and meet the conditions given.
For example
if (row[4].toString().toLowerCase() == anotherRow[4]){
//then display all rows which match that specific value
// e.g:
row[4]. Display everything that matches anotherRow only;
}
this is what I can't get my head around displaying all rows that meet that criteria only, currently I'm able to display all row[4], row[4] is the column
This script that will get a range of data and store it as an array of arrays.
function getMultipleValues(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
// getRange exp. -
//sheet.getRange(first row, first column, last row, last column)
// Get a grid of data 4 rows and two columns
var range = sheet.getRange(1,1,4,2);
var dataGrid = range.getValues()
Logger.log(dataGrid)
//[["Fruit","quantity"],["pears",1],["apples",1],["grapes",3]]
//Get a column of data
var range = sheet.getRange(1,1,4,1);
var dataColumn = range.getValues()
Logger.log(dataColumn)
//[["Fruit"],["pears],["apples"],["grapes"]]
//If the column length is unknown. Get everything.
//Be sure to delete rows that are not needed in the sheet when using this.
var range = sheet.getRange("A1:A");
var allOfColumnA = range.getValues()
Logger.log(allOfColumnA)
//[["Fruit"],["pears],["apples"],["grapes"],[],[],[],[]]
}
Building on Supertopaz' answer, it looks like the filter method of an array will work to remove rows that don't match your criteria. For example, given a set of data like this:
Source data for function
The following function will match the data to the criteria you specify, and write the matches to a second sheet.
function writeMatches() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName('Sheet1');
var sheet2 = ss.getSheetByName('Sheet2');
var criteria = sheet1.getRange(1,2,1,1).getValue(); // returns a single value if the range is a single cell
var arData = sheet1.getRange(4,1,5,2).getValues(); // returns a 2d array
var arOutput = [];
var rangeOutput;
arOutput = arData.filter(function(row){
return row[1].toLowerCase() === criteria
/* The callback function iterates through the array.
When this expression evaluates to true, the
element is included in the output array.
The callback function can also supply an optional
index argument, if you want to compare symmetric
arrays.
*/
});
rangeOutput = sheet2.getRange(1,1,arOutput.length,arOutput[0].length);
rangeOutput.setValues(arOutput);
}
I have a google spreadsheet which i've made public and all and want to go from that to a JS array. I've been playing with it and trying to convert it into a JSON then into an array but i'm having no luck. Is there a way to go from my spreadsheet to a 2d JS array which I can then run functions on.
So if the spreadsheet looks like this:
NAME COLOR SIZE
MARK BLUE 6
DAVE RED 8
The array will be a 2d array such that value [0][0] will be MARK, [1][0] will be BLUE and [0][1] will be DAVE etc.
Any help would be much appreciated
Edit: since google API4 is our, and the module was updated accordingly, and since API3 will be discontinued on September, I updated the code accordingly.
I know this is old now, but in case any one else searching for an answer:
I played around with manipulating google spreadsheet in JS, and wanted to go from a worksheet with several sheets, to an array I can easily use in other functions.
So I wrote a program to access a Worksheet, get all of the sheets (tab) from within, reduce the all-empty rows and columns, and return the a new worksheet array.
Each element in the worksheet array represents the sheet from the Excel worksheet.
Also, each element has a 2d array of its cells, as such:
cellsArr:[[row],[col]]
To run the program, simply replace YOUR_CLIENT_SECRET.json to your client secret json file, and ENTER_GOOGLE_SPREADSHEET_ID_FROM_URL to the ID of the spreadsheet from the URL. For example:
docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit#gid=01
and then run:
getWorksheet(ENTER_GOOGLE_SPREADSHEET_ID_FROM_URL);
This returns a promise, which is resolved into the revised worksheet (as described above).
Please note, reduceRowsCols() deletes empty rows and columns from each sheet. It deletes each row where the first column cell is empty, and every column where the first row cell is empty, so every row where [i][0]==null and every column where [0][i]==null.
Simply remove reduceRowsCols() from the code to negate that
const fs = require('fs');
const {GoogleSpreadsheet} = require('google-spreadsheet'),
creds = require('./client_secret.json'),
/**
* #Description Authenticate with the Google Spreadsheets API using credentials json.
* #param {string} spreadsheet_id - spreadsheet id to get. Get this from the URL of the spreadsheet
* Returns the entire raw worksheet.
* */
async function accessSpreadsheet(spreadsheet_id) {
const doc = new GoogleSpreadsheet(spreadsheet_id);
await doc.useServiceAccountAuth(creds);
await doc.loadInfo();
console.log(`${getCurrentTime()}: Loaded raw doc ${doc.title}`);
return doc;
}
/**
* #Description This is an async function to retrieve the raw worksheet
* #param {string} spreadsheet_id - spreadsheet id to get. Get this from the URL of the spreadsheet
* */
async function getWorksheet(spreadsheet_id) {
try {
let res = await accessSpreadsheet(spreadsheet_id);
console.log(`${getCurrentTime()}: Organizing sheets for extraction...`);
res = await getCondensedWorksheets(res.sheetsByIndex);
createSheetsCopy(res);
return res;
} catch (e) {
throw (e);
}
}
/**
* #param worksheetsArr
*/
async function getCondensedWorksheets(worksheetsArr) {
if (!Array.isArray(worksheetsArr)) {
throw `getCondensedWorksheets: worksheets variable passed is not an array. Object passed:\n${worksheetsArr}`;
}
let revisedArr = [];
for (let i = 0; i < worksheetsArr.length; i++) {
// for (let i = 0; i < 1; i++) {
revisedArr.push(await worksheetsArr[i].getRows().then((res)=>{
let thisRevised = {
id: worksheetsArr[i]._rawProperties.sheetId,
title: worksheetsArr[i].title,
rowCount: worksheetsArr[i].rowCount,
colCount: worksheetsArr[i].columnCount,
getRows: worksheetsArr[i].getRows,
getHeaders: worksheetsArr[i].headerValues,
getCells : res.map( row => {return(row._rawData);}),
resize: worksheetsArr[i].resize,
}
return getCells2dArray(thisRevised);
}))
}
return Promise.all(revisedArr).then(()=>{
return revisedArr;
})
}
/**
* #param {array} thisSheet - a single sheet (tab)
*/
function getCells2dArray(thisSheet) {
let sheetCellsArr = [];
sheetCellsArr.push(thisSheet.getHeaders);
thisSheet.getCells.map(row => {
sheetCellsArr.push(row);
})
thisSheet.cellsArr = sheetCellsArr;
delete thisSheet.getCells;
reduceRowsCols(thisSheet);
return thisSheet;
}
function reduceRowsCols(thisSheet) {
for (let i = 0; i < thisSheet.cellsArr.length; i++) {
if (thisSheet.cellsArr[i][0] == null) {
thisSheet.cellsArr.slice(0, i);
break;
}
}
for (let i = 0; i < thisSheet.cellsArr[0].length; i++) {
if (thisSheet.cellsArr[0][i] == null) {
thisSheet.cellsArr[0].slice(0, i);
break;
}
}
}
const getCurrentTime = function (){
var date = new Date();
return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}:${date.getMilliseconds()}`
}
module.exports = {getWorksheet};
There are two methods in GAS that allow for reading values in cells. They are .getValue() and .getValues(). As it is implied, .getValue() retrieves only the value of the top left cell in a range. .getValues() retrieves a 2D array of all values in your range.
The code below defines the variables necessary to read the range that follows. The range is defined (based off your example) as starting at the second row (ignores your header row), continuing for 3 columns and down to the last row with content.
You'll notice that we find .getLastRow() but then define the number of rows to read as lastRow - 1. This is because .getLastRow() gives us the position of the last row with data; or more simply, an integer of how many rows have data. If we have 20 rows with data, that includes the header row which we would like to ignore. Reading 20 rows after shifting our range down a row will include the first empty row. Reading 19 rows instead stops after reading the last data-filled row.
Note that .getRange() requires the input of .getRange(startRow, startColumn, numRows, numColumns). This can be found with more detail and explanation in the GAS Reference sheet here
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSpreadsheet();
var lastRow = sheet.getLastRow();
var range = sheet.getRange(2, 1, (lastRow - 1), 3);
var dataArray = range.getValues();
}
Values in this array can then be called, defined, read, etc. as you would expect for Javascript. You will find in the GAS Reference that many methods have a singular and plural version such as .getBackground() and .getBackgrounds(). These will act in the same comparative way as .getValue() vs. .getValues().
It may also be helpful to note that even if your range is a singular row but many columns, .getValues() gives you a 2D array where all values will start with [0] as the only possible value for row is 0.
I have two worksheets in my google spreadsheet:
Input data is coming into the Get Data worksheet via the importxml function.
However, I would like to copy all values of the Get Data sheet to the Final Data sheet and if there are duplicates(in terms of rows) append the unique row.
Here is what I tried:
function onEdit() {
//get the data from old Spreadsheet
var ss = SpreadsheetApp.openById("1bm2ia--F2b0495iTJotp4Kv1QAW-wGUGDUROwM9B-D0");
var dataRange = ss.getSheetByName("Get Data").getRange(1, 1, ss.getLastRow(), ss.getLastColumn());
var dataRangeFinalData = ss.getSheetByName("Final Data").getRange(1, 1, ss.getLastRow(), ss.getLastColumn());
var myData = dataRange.getValues();
//Open new Spreadsheet & paste the data
newSS = SpreadsheetApp.openById("1bm2ia--F2b0495iTJotp4Kv1QAW-wGUGDUROwM9B-D0");
Logger.log(newSS.getLastRow());
newSS.getSheetByName("Final Data").getRange(newSS.getLastRow()+1, 1, ss.getLastRow(), ss.getLastColumn()).setValues(myData);
//remove duplicates in the new sheet
removeDups(dataRangeFinalData)
}
function getId() {
Browser.msgBox('Spreadsheet key: ' + SpreadsheetApp.getActiveSpreadsheet().getId());
}
function removeDups(array) {
var outArray = [];
array.sort(lowerCase);
function lowerCase(a,b){
return a.toLowerCase()>b.toLowerCase() ? 1 : -1;// sort function that does not "see" letter case
}
outArray.push(array[0]);
for(var n in array){
Logger.log(outArray[outArray.length-1]+' = '+array[n]+' ?');
if(outArray[outArray.length-1].toLowerCase()!=array[n].toLowerCase()){
outArray.push(array[n]);
}
}
return outArray;
}
Below you can find the link to a sample spreadsheet:
Sample Sheet
My problem is that the data does not get pasted.
I appreciate your replies!
tl;dr: See script at bottom.
An onEdit() function is inappropriate for your use case, as cell contents modified by spreadsheet functions are not considered "edit" events. You can read more about that in this answer. If you want this to be automated, then a timed trigger function would be appropriate. Alternatively, you could manually invoke the function by a menu item, say. I'll leave that to you to decide, as the real meat of your problem is how to ensure row-level uniqueness in your final data set.
Merging unique rows
Although your original code is incomplete, it appears you were intending to first remove duplicates from the source data, utilizing case-insensitive string comparisons. I'll suggest instead that some other JavaScript magic would help here.
We're interested in uniqueness in our destination data, so we need to have a way to compare new rows to what we already have. If we had arrays of strings or numbers, then we could just use the techniques in How to merge two arrays in Javascript and de-duplicate items. However, there's a complication here, because we have an array of arrays, and arrays cannot be directly compared.
Hash
Fine - we could still compare rows element-by-element, which would require a simple loop over all columns in the rows we were comparing. Simple, but slow, what we would call an O(n2) solution (Order n-squared). As the number of rows to compare increased, the number of unique comparison operations would increase exponentially. So, let's not do that.
Instead, we'll create a separate data structure that mirrors our destination data but is very efficient for comparisons, a hash.
In JavaScript we can quickly access the properties of an object by their name, or key. Further, that key can be any string. We can create a simple hash table then, with an object whose properties are named using strings generated from the rows of our destination data. For example, this would create a hash object, then add the array row to it:
var destHash = {};
destHash[row.join('')] = true; // could be anything
To create our key, we're joining all the values in the row array with no separator. Now, to test for uniqueness of a row, we just check for existence of an object property with an identically-formed key. Like this:
var alreadyExists = destHash.hasOwnProperty(row.join(''));
One additional consideration: since the source data can conceivably contain duplicate rows that aren't yet in the destination data, we need to continuously expand the hash table as unique rows are identified.
Filter & Concatenate
JavaScript provides two built-in array methods that we'll use to filter out known rows, and concatenate only unique rows to our destination data.
In its simple form, that would look like this:
// Concatentate source rows to dest rows if they satisfy a uniqueness filter
var mergedData = destData.concat(sourceData.filter(function (row) {
// Return true if given row is unique
}));
You can read that as "create an array named mergedData that consists of the current contents of the array named destData, with filtered rows of the sourceData array concatenated to it."
You'll find in the final function that it's a little more complex due to the other considerations already mentioned.
Update spreadsheet
Once we have our mergedData array, it just needs to be written into the destination Sheet.
Padding rows: The source data contains rows of inconsistent width, which will be a problem when calling setValues(), which expects all rows to be squared off. This will require that we examine and pad rows to avoid this sort of error:
Incorrect range width, was 6 but should be 5 (line ?, file "Code")
Padding rows is done by pushing blank "cells" at the end of the row array until it reaches the intended length.
for (var col=mergedData[row].length; col<mergedWidth; col++)
mergedData[row].push('');
With that taken care of for each row, we're finally ready to write out the result.
Final script
function appendUniqueRows() {
var ss = SpreadsheetApp.getActive();
var sourceSheet = ss.getSheetByName('Get Data');
var destSheet = ss.getSheetByName('Final Data');
var sourceData = sourceSheet.getDataRange().getValues();
var destData = destSheet.getDataRange().getValues();
// Check whether destination sheet is empty
if (destData.length === 1 && "" === destData[0].join('')) {
// Empty, so ignore the phantom row
destData = [];
}
// Generate hash for comparisons
var destHash = {};
destData.forEach(function(row) {
destHash[row.join('')] = true; // could be anything
});
// Concatentate source rows to dest rows if they satisfy a uniqueness filter
var mergedData = destData.concat(sourceData.filter(function (row) {
var hashedRow = row.join('');
if (!destHash.hasOwnProperty(hashedRow)) {
// This row is unique
destHash[hashedRow] = true; // Add to hash for future comparisons
return true; // filter -> true
}
return false; // not unique, filter -> false
}));
// Check whether two data sets were the same width
var sourceWidth = (sourceData.length > 0) ? sourceData[0].length : 0;
var destWidth = (destData.length > 0) ? destData[0].length : 0;
if (sourceWidth !== destWidth) {
// Pad out all columns for the new row
var mergedWidth = Math.max(sourceWidth,destWidth);
for (var row=0; row<mergedData.length; row++) {
for (var col=mergedData[row].length; col<mergedWidth; col++)
mergedData[row].push('');
}
}
// Write merged data to destination sheet
destSheet.getRange(1, 1, mergedData.length, mergedData[0].length)
.setValues(mergedData);
}