Output dictonary to a table: key order issue - javascript

I have a JSON file and I want to output the data to a table in Google Sheet. Since the data order is not guaranteed, I need to search for the corresponding row for the second and subsequent data set (Tuesday data from my example below).
Apart from looping thru the first column, is there a faster or more elegant way to do this?
function test(){
var ss=SpreadsheetApp.getActiveSpreadsheet();
var SSheet=ss.getActiveSheet();
var data={Monday:{Apple:2, Orange:3}, Tuesday:{Orange:4, Apple:5}};//intentionally swap the key
var row=2;
var column=2;
var bWriteHeader=true;
for (const g in data){
SSheet.getRange(1,column).setValue(g)
row=2;
for (const k in data[g]){
if (bWriteHeader){
SSheet.getRange(row,1).setValue(k);
SSheet.getRange(row,column).setValue(data[g][k]);
row++;
}else{
//Search for the corresponding row--- how to do it elegantly, apart from looping thru first column?
SSheet.getRange(row,column).setValue(data[g][k]);
}
}
bWriteHeader=false;
column++;
}
}
Desired output:
Monday Tuesday
Apple 2 5
Orange 3 4

I believe your goal as follows.
You want to reduce the process cost of your script.
Although I'm not sure whether my proposed method is more elegant way, in this answer, how about the following flow?
Retrieve a row header.
Retrieve a column header.
Create an array using the row header and column header.
Put the values to the active sheet.
In this flow, after the array was created, the array is put to the Spreadsheet. By this, I thought that the process cost will be lower than your script that setValue is used in a loop. Ref When this flow is reflected to the script, it becomes as follows.
Sample script:
function myFunction() {
var data = { Monday: { Apple: 2, Orange: 3 }, Tuesday: { Orange: 4, Apple: 5 } };//intentionally swap the key
// 1. Retrieve a row header.
var rowHeader = Object.keys(data).sort();
// 2. Retrieve a column header.
var colHeader = Object.keys(data[rowHeader[0]]).sort();
// 3. Create an array using the row header and column header.
var values = rowHeader.reduce((ar, k) => ar.concat([[k, ...colHeader.map(l => data[k][l])]]), [["", ...colHeader]]);
var res = values[0].map((_, i) => values.map(r => r[i]));
// 4. Put the values to the active sheet.
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.getRange(1, 1, res.length, res[0].length).setValues(res);
}
Testing:
var data = { Monday: { Apple: 2, Orange: 3 }, Tuesday: { Orange: 4, Apple: 5 } };//intentionally swap the key
// 1. Retrieve a row header.
var rowHeader = Object.keys(data).sort();
// 2. Retrieve a column header.
var colHeader = Object.keys(data[rowHeader[0]]).sort();
// 3. Create an array using the row header and column header.
var values = rowHeader.reduce((ar, k) => ar.concat([[k, ...colHeader.map(l => data[k][l])]]), [["", ...colHeader]]);
var res = values[0].map((_, i) => values.map(r => r[i]));
console.log(res)
References:
Benchmark: Reading and Writing Spreadsheet using Google Apps Script
map()
Object.keys()
Object.values()

Related

Google Sheets Scripts - How to get a cell value with corresponding category

In Google Apps Script, I'm trying to get a value of a cell that has a particular category.
My data:
Category
Value
Work Expenses
£15.00
Work Expenses
£15.00
Pets
£25.99
Food & Drink
£38.50
Work Expenses
£11.48
So in my script I need a way to select the corresponding values of my "work expenses" category.
Solution:
You can directly use filter to select only the Work Expenses category and then use map to get the values of the second column.
Example script:
function myFunction() {
const ss = SpreadsheetApp.getActive();
const sheet = ss.getSheetByName("Sheet1") // name of the sheet
const data = sheet.getRange('A2:B'+sheet.getLastRow()).getValues(); // select A2:B
const fdata = data.filter(r=>r[0]=="Work Expenses").map(r=>r[1]);
console.log(fdata);
}
Example input:
Example output:
If you need a script it could be something like this:
function main() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var range = sheet.getRange('A2:B');
var cells = get_cells_by_category(range, 'Work Expenses');
console.log(cells);
}
function get_cells_by_category(range, category) {
var data = range.getValues();
var cells = [];
for (let row of data) {
if (row[0] == category) cells.push(row[1]);
}
return cells;
}
But if it's a big project and you need to get values many times it makes sense to make an object from the data. The categories could be keys of the object and arrays of the cells could be values of the object:
var obj = {
'Work Expenses': [ '£15.00', '£15.00', '£11.48' ],
'Pets': [ '£25.99' ],
'Food & Drink': [ '£38.50' ]
}
console.log(obj['Work Expenses']);
console.log(obj['Pets']);
This way you can get the values much faster. No need to loop every time through all the data.

Use array of invoice numbers to create invoice objects within which each invoice number from the initial array serves as id property

Building a script in google apps script.
I get values from an invoice data sheet with multiple lines per invoice so as to account for line items.
My progress so far has been to extract individual invoice numbers from the column (each invoice number occurs as many line items the individual invoice has).
The array todaysInvoices looks like this: [35033817, 35033818, 35033819, 35033820, 35033821]
Now, I need a way to create an object for each of these invoice numbers that has different properties (such as invoiceDate and customerName etc.). The initial invoice number as in the array should thereby be assigned as 'id' property to the new invoice object.
I need help to use objects in javascript.
If you require additional information, please let me know.
Below is a screenshot of a simplified version of my order sheet:
This is a clipping of my order sheet. Before and after the shown columns there are many more with more details but the hierarchies of information are already in the image
Below is the code I have so far:
const orderSheet = SpreadsheetApp.openById('SPREADSHEETID').getSheetByName('SHEETNAME');
const invoiceTemplate = DriveApp.getFileById('DOCUMENTID');
const tempFolder = DriveApp.getFolderById('FOLDERID');
const invoiceData = orderSheet.getRange(4,7, orderSheet.getLastRow() - 1, 57).getDisplayValues().filter(function (rows){ return rows[0] === 'INVOICED'});
const invDataRepo = SpreadsheetApp.openById('SPREADSHEETID2');
var timestamp = new Date();
function printBulkInvoices() {
logLineItems ();
var todaysInvoices = uniqueInvIDs ();
todaysInvoices.sort();
todaysInvoices.map(String);
//fetchInvData (todaysInvoices);
Logger.log (todaysInvoices)
}
function fetchInvData (invoiceIDs) {
let invoices = {
}
Logger.log(invoices)
invoiceIDs.forEach
}
function fetchLineItems (invoiceDataArray) {
}
// send array of todays unique invoice numbers (later all inv data?) to invdata sheet and log them
function logTodaysInvoices (invIDArr){
invIDArr.forEach
invDataRepo.getSheetByName('invdata').getRange(invDataRepo.getSheetByName('invdata').getLastRow()+1,1,invIDArr.length,1).setValue(invIDArr);
}
// return an array of unique invoice ids from todays invoice data
function uniqueInvIDs (){
let singleArray = invoiceData.map(row => row[5]);
let unique = [...new Set(singleArray)];
return unique;
}
//log incoicedata to invdatarepo-sheet 'lineitems'
function logLineItems (){
invDataRepo.getSheetByName('lineitems').getRange(invDataRepo.getSheetByName('lineitems').getLastRow()+1,2,invoiceData.length,invoiceData[0].length).setValues(invoiceData);
}
It's hard to say exactly what you need since we cannot see your Invoice Data Sheet.
But here's something that might give you a start:
let iobj = {idA:[]};
[35033817, 35033818, 35033819, 35033820, 35033821].forEach((id => {
if(!iobj.hasOwnProperty(id)) {
iobj[id]={date: invoiceDate, name: customName, items:[]};
iobj.idA.push(id);//I find it handy to have an array of object properties to loop through when I wish to reorganize the data after it's all collected
} else {
iobj[id].items.push({item info properties});//I am guessing here that you may wish to addition additional information about the items which are on the current invoice
}
});
Javascript Object
To follow up from your question:
Your loop to collect object data would start to look something like this:
function getInvoiceData() {
const ss = SpreadsheetApp.getActive();
const ish = ss.getSheetByName('Invoice Data');
const isr = 2;
const hA = ish.getRange(1, 1, 1, ish.getLastColumn()).getValues()[0];
let idx = {};//object return head index into row array based on header title which in this case I assume invoice number is labeled 'Invoicenumber'
hA.forEach((h, i) => {idx[h] = i});
const vs = ish.getRange(isr, 1, ish.getLastRow() - isr + 1, ish.getLastColumn()).getValues();
let iobj = { idA: [] };
vs.forEach(r => {
if (!iobj.hasOwnProperty(r[idx['invoicenumber']])) {
iobj[r[idx['invoicenumber']]] = { date: r[idx['invoicedate']], name: r[idx['customername']], items: [] };
iobj.idA.push(r[idx['invoicenumber']]);
} else {
iobj[r[idx['invoicenumber']]].items.push({ iteminfoproperties:'' });
}
});
}

Filtering google sheets import data before import

I have a script that imports data from a google sheet. Before importing the data into the new sheet I would like to filter it. In Column2 are our Cost centers listed and all cost centers starting with '41' should be filtered and imported to the sheet. Right now I'm a little bit blocked in saying filtering by column2 where the string startswith '41'.
// Gets the active sheet.
var sheet = SpreadsheetApp.getActiveSheet();
// Gets a different spreadsheet from Drive using
// the spreadsheet's ID.
var employeeActuals = SpreadsheetApp.openById(
"1yL_0eB9b6CQLOshjPglDA-MnP2HZdLeIrKh4DO-qN0c"
);
// Gets the sheet, data range, and values of the
// spreadsheet stored in employeeActuals.
var employeeActualsSheet = employeeActuals.getSheetByName("Overview")
var range = employeeActualsSheet.getDataRange();
var rangeValues = range.getValues();
var databasis = rangeValues.filter(function(Item){return Item[1] === String().startsWith(["41"]) })
Logger.log(databasis); ```
Try something like this:
function myfunk() {
const sheet = SpreadsheetApp.getActiveSheet();
const ss = SpreadsheetApp.openById(gobj.globals.testsourceid);
const esh = ss.getSheetByName("Sheet1")
const range = esh.getDataRange();
const vs = range.getValues();
let data = vs.filter(e => e[0].startsWith('17'))
Logger.log(data);
}
Data:
COL1
COL2
COL3
10,1
10,2
10,3
11,1
11,2
11,3
12,1
12,2
12,3
13,1
13,2
13,3
14,1
14,2
14,3
15,1
15,2
15,3
16,1
16,2
16,3
17,1
17,2
17,3
18,1
18,2
18,3
19,1
19,2
19,3
20,1
20,2
20,3
output:
12:09:45 PM Notice Execution started
12:09:45 PM Info [[17,1, 17,2, 17,3]]
12:09:47 PM Notice Execution completed
The getValues() method returns a two dimensional string so in order to filter out the values, you will also need a loop in order to the filtering properly:
var filterVals = [];
for (let i = 0; i < rangeValues.length; i++) {
var values = rangeValues[i].filter(element => element.toString().startsWith('41'));
filterVals.push(values);
}
let databasis = filterVals.filter(element => element.toString() != "")
Logger.log(databasis);
Reference
Apps Script Range Class getValues().
You can filter data before importing. Gsheets allow doing that with a query formula or a filter formula.
The flow will be employeeActualsSheet -> filtered employeeActualsSheet -> import data.
Question - what is your data destination?

Cross reference names on spreadsheets to get specific data points (looping through an array)

I have two sheets. Test Data has 3-4k entries of many columns of data and Order Changes has no data at all. I would like to search two specific columns on Test Data, a column of names and a column of yes or no. If column two of Test Data contains a 'yes' in the cell then the name of that person would be placed into a cell on order changes.
This is what I have so far:
function isThreshold(){
var data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Test Data");
var cdata = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Order Changes");
var lc = data.getLastColumn();
var lookUp = data.getRange(1,6,3,2).getValues();
lookUp.forEach(var info in lookUp){
}
Logger.log(lookUp);
}
I probably shouldn't loop through that many entries but I don't know of any other way. Should I combine the forEach loop with an if loop to get the desired result or use some other method?
I believe your goal as follows.
You want to retrieve the values from the cells "F1:G" of sheet "Test Data".
You want to search yes from the column "G" and when the column "G" is yes, you want to put the value of the column "F" to the sheet "Order Changes".
Modification points:
SpreadsheetApp.getActiveSpreadsheet() can be declared one time.
In this case, you can retrieve the values from the range of "F1:G" + data.getLastRow() of "Test Data", and create the array for putting to the sheet "Order Changes", and put it.
When above points are reflected to your script, it becomes as follows.
Modified script:
function isThreshold(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = ss.getSheetByName("Test Data");
var cdata = ss.getSheetByName("Order Changes");
var valuesOfTestData = data.getRange("F1:G" + data.getLastRow()).getValues();
var valuesForOrderChanges = valuesOfTestData.reduce((ar, [f, g]) => {
if (g.toLowerCase() == "yes") ar.push([f]);
return ar;
}, []);
if (valuesForOrderChanges.length > 0) {
cdata.getRange(1, 1, valuesForOrderChanges.length, valuesForOrderChanges[0].length).setValues(valuesForOrderChanges);
// or cdata.getRange(cdata.getLastRow() + 1, 1, valuesForOrderChanges.length, valuesForOrderChanges[0].length).setValues(valuesForOrderChanges);
}
}
In this modified script, from your question, it supposes that the columns "F" and "G" are the value of name and yes or no.
References:
getRange(a1Notation) of Class Sheet
reduce()

Steps to create javascript objects from text data out of a csv file using no libraries

I need to convert data from a csv file into a JavaScript object where key for each object is the id from the data. One JS object per each user.
Question: Is there a way to solve it using vanilla JS?
I need at the least a general walkthrough of what to do cause all I've been able to find is methods on how to solve this by using jQuery or JSON but for the research I am trying to understand, I can't use a specialized library, I'm only allowed to use plain VanillaJS.
Sample text data:
id first_name last_name email gender
-- ---------- --------- ----- ------
1 Gloria Mendez gmendez#ipu.gov Female
2 Lucy Grey lgrey#gmail.com Female
3 Max Mcolom mmcolom#yahoo.com Male
4 David Cooke dcooke#gmail.com Male
5 Marwin Darge mdarge#gov.com Male
hundreds of other rows
Desired output:
{
1: {
id: 1, first_name: 'Gloria', last_name: 'Mendez', email: 'gmendez#ipu.gov', gender: 'female'
},
2: {
id: 1, first_name: 'Lucy', last_name: 'Grey', email: 'lgrey#gmail.com', gender: 'female'
},
...
}
Using vanilla JS below.
I've added comments to explain what the code is doing. It;s basically splitting the data into rows, and then cells, and doing array manipulation along the way:
let table =
`id first_name last_name email gender
-- ---------- --------- ----- ------
1 Gloria Mendez gmendez#ipu.gov Female
2 Lucy Grey lgrey#gmail.com Female
3 Max Mcolom mmcolom#yahoo.com Male
4 David Cooke dcooke#gmail.com Male
5 Marwin Darge mdarge#gov.com Male`
// Split into rows, and then split each row into cells
let rows = table.split(/[\r\n]+/g).map(row => row.trim().split(/[\s\t]+/g));
// The first row will be the header
let header = rows[0];
// The rest is the data
let data = rows.slice(2);
// Create a function to return the desired object structure
function formatObject(headers, cells) {
return headers.reduce((result, header, idx) => {
result[header] = cells[idx];
return result;
}, {});
}
// Reduce each row into the desired format, and use the ID as a key
let result = data.reduce((res, row, idx) => {
let value = formatObject(header, row);
res[value.id] = value;
return res;
}, {});
// Log the result
console.log(result);
Parse the file
split on line break \n, to get rows
const rows = csv.split('\n');
split each row on comma , to get cells (if it's CSV, otherwise, \t for TSV)
const rawData = rows.map(d => d.split(',');
Extract the headers from the 1st row
The first row of data is you list of keys to build objects
const headers = rawData[0]
Convert rows into objects
const data = rawData.slice(2); // get rid of first 2 rows
const output = data.map(row => {
const obj = {};
headers.forEach((h, i) => obj[h] = row[i] });
return obj;
});
Of course, you need to handle all sorts of edge cases, like commas in the CSV cell, missing data etc...
I hope this gets you started.
Assuming you are loading the data already, you could loop through the data and use javascripts object bracket notation to define the new properties based on the id.
If you are looking for step by step,
first load the csv using something like node's fs module or the filereader in the browser.
for each line read parse the csv with javascript split.
if the assuming the id is always the first column you can now get
the id of the data with the splitArray[0].
Create a new object, then use bracket notation to set the property
of the object to the id. ex.
var obj = {};
obj[splitArray[0]] = data;
This is really rough, untested, but should get you closer to where you need to go.
Assuming a double quoted comma separated file which is about the most common csv format:
/* empty lines shown on purpose to approximate real file */
const csvString =
`"id","first_name","last_name","email","gender"
"1","Gloria","Mendez","gmendez#ipu.gov","Female"
"2","Lucy","Grey","lgrey#gmail.com","Female"
"3","Max","Mcolom","mmcolom#yahoo.com","Male"
"4","David","Cooke","dcooke#gmail.com","Male"
"5","Marwin","Darge","mdarge#gov.com","Male"`;
//split string on line breaks into array of line strings
const csvLines=csvString.split('\n');
// filter empty lines out and map each line to sub array
let arr = csvLines.filter(s=>s.trim()).map(arrFromLine)
// get headings from first line
let headings = arr.shift();
// map all the other lines into objects
let res = arr.map(lineArr =>{
return lineArr.reduce((a,c, i)=>{
a[headings[i]] = c;
return a
},{})
});
// map lines to array helper function
function arrFromLine(str){
// remove quotes on each end, split at `","` to avoid any other commas in text
return str.trim().slice(1, str.length-1).split('","');
}
console.log(res)
.as-console-wrapper { max-height: 100%!important;}

Categories