I have a CSV file like this one below, I must take some different values from it, but I'm struggling in transform this CSV into an array of Objects
"+++++++++ 1.2 LifeTime Cost and Emissions +++++++++","<TABLE>"
" ","year1","year2","year3","year4","year5","year6","year7","year8","year9","year10","year11","year12","year13","year14","year15","year16","year17","year18","year19","year20","year21","year22","year23","year24","year25","<HEADER>"
"Total Annual Energy Costs (incl. annualized capital costs and electricity sales) ($)",-560.9845,353.4204,451.6855,514.2567,523.2091,572.8177,622.6726,632.3996,642.4129,652.7211,663.3330,674.2575,1458.1040,617.1780,661.0587,692.5061,705.1385,732.5260,760.2972,774.0806,788.2706,802.8795,817.9194,833.4033,849.3444
"Total Annual CO2 emissions (kg)",387734.0330,387734.0330,387736.8925,387736.8925,387736.8925,387738.4191,387738.4191,387738.4191,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886
"Levelized Cost Of Energy ($/kWh)",-0.1738,0.1095,0.1404,0.1598,0.1626,0.1786,0.1942,0.1972,0.2003,0.2035,0.2069,0.2103,0.4547,0.1925,0.2061,0.2159,0.2199,0.2284,0.2371,0.2414,0.2458,0.2504,0.2551,0.2599,0.2649
I've tryed this:
const csvFilePath = 'Result_Lifetime002.csv';
const json = await csvToJson().fromFile(csvFilePath);
const jsonString = JSON.stringify(json, null, 2);
But it returns a big string with an array in it.
Anyway, the expected result should be something like this (just taking the item 1.2 as an example):
const result = [
{
"+++++++++ 1.2 LifeTime Cost and Emissions +++++++++":"Total Annual Energy Costs (incl. annualized capital costs and electricity sales) ($)",
"year1": -560.9845,
"year2": 353.4204,
"year3": 451.6855,
"year4": 514.2567,
"year5": 523.2091,
"year6": 572.8177,
"year7": 622.6726,
"year8": 632.3996,
"year9": 642.4129,
"year10": 652.7211,
"year11": 663.3330,
"year12": 674.2575,
"year13": 1458.1040,
"year14": 617.1780,
"year15": 661.0587,
"year16": 692.5061,
"year17": 705.1385,
"year18": 732.5260,
"year19": 760.2972,
"year20": 774.0806,
"year21": 788.2706,
"year22": 802.8795,
"year23": 817.9194,
"year24": 833.4033,
"year25": 849.3444
},
{
"+++++++++ 1.2 LifeTime Cost and Emissions +++++++++":"Total Annual CO2 emissions (kg)",
"year1": 387734.0330,
"year2": 387734.0330,
"year3": 387736.8925,
"year4": 387736.8925,
"year5": 387736.8925,
"year6": 387738.4191,
"year7": 387738.4191,
"year8": 387738.4191,
"year9": 387738.8886,
"year10": 387738.8886,
"year11": 387738.8886,
"year12": 387738.8886,
"year13": 387738.8886,
"year14": 387738.8886,
"year15": 387738.8886,
"year16": 387738.8886,
"year17": 387738.8886,
"year18": 387738.8886,
"year19": 387738.8886,
"year20": 387738.8886,
"year21": 387738.8886,
"year22": 387738.8886,
"year23": 387738.8886,
"year24": 387738.8886,
"year25": 387738.8886,
},
{
"+++++++++ 1.2 LifeTime Cost and Emissions +++++++++":"Levelized Cost Of Energy ($/kWh)",
"year1": -0.1738,
"year2": 0.1095,
"year3": 0.1404,
"year4": 0.1598,
"year5": 0.1626,
"year6": 0.1786,
"year7": 0.1942,
"year8": 0.1972,
"year9": 0.2003,
"year10": 0.2035,
"year11": 0.2069,
"year12": 0.2103,
"year13": 0.4547,
"year14": 0.1925,
"year15": 0.2061,
"year16": 0.2159,
"year17": 0.2199,
"year18": 0.2284,
"year19": 0.2371,
"year20": 0.2414,
"year21": 0.2458,
"year22": 0.2504,
"year23": 0.2551,
"year24": 0.2599,
"year25": 0.2649
}
]
Would something like this work for you?
For simplicity's sake, I've placed the contents of your CSV inside a variable, and skipped the steps of reading the file (I'll give this code at very end). Please note that there are most likely more optimal ways of dealing with this, but I decided on going with this solution, since I could break it down into simple steps.
var data = `
+++++++++ 1.2 LifeTime Cost and Emissions +++++++++,<TABLE>
,year1,year2,year3,year4,year5,year6,year7,year8,year9,year10,year11,year12,year13,year14,year15,year16,year17,year18,year19,year20,year21,year22,year23,year24,year25,<HEADER>
Total Annual Energy Costs (incl. annualized capital costs and electricity sales) ($),-560.9845,353.4204,451.6855,514.2567,523.2091,572.8177,622.6726,632.3996,642.4129,652.7211,663.3330,674.2575,1458.1040,617.1780,661.0587,692.5061,705.1385,732.5260,760.2972,774.0806,788.2706,802.8795,817.9194,833.4033,849.3444
Total Annual CO2 emissions (kg),387734.0330,387734.0330,387736.8925,387736.8925,387736.8925,387738.4191,387738.4191,387738.4191,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886,387738.8886
Levelized Cost Of Energy ($/kWh),-0.1738,0.1095,0.1404,0.1598,0.1626,0.1786,0.1942,0.1972,0.2003,0.2035,0.2069,0.2103,0.4547,0.1925,0.2061,0.2159,0.2199,0.2284,0.2371,0.2414,0.2458,0.2504,0.2551,0.2599,0.2649
`;
// Create an array by splitting the CSV by newline
data = data.trim().split(/\r?\n/);
// Extract the first index, since we want to repeat it later on as
// the first key of every JSON element of your resulting array
// This is the long string with the pluses
/* remove this multiline comment to see the contents of data[0]
console.log(data[0]);
*/
var title = data[0].split(",")[0];
// Extract the other main keys - years
var innerKeys = data[1].trim().split(",");
// Remove the the last one, since we don't need it - <HEADER>
innerKeys.pop();
// Prepare the array for our results
var results = [];
// Loop through indivdual rows, and split by comma / ,
// We'll skip the first two, since we've dealt with them
// data[0] being the row with the long string with the pluses
// and data[1] being the row containing the years
for(var i = 2; i < data.length; i++) {
// Let's clean any trailing empty characters
var tempRow = data[i].trim();
// If there's anything we can work with
if(tempRow) {
// Create an array from the values in the current line
tempRow = tempRow.split(",");
// Let's get the value for our first keys
// These are the Total Annual etc strings from your CSV
var tempTitle = tempRow[0];
// Let's declare and fill our temp object
var innerJSON = {};
// The first key is the one with the pluses
// and its value is the Total Annual etc string
innerJSON[title] = tempTitle;
for(var j = 1; j < tempRow.length; j++) {
// Let's fill the years and give them matching values
innerJSON[innerKeys[j]] = tempRow[j];
}
// All done, add it to the resulting array
results.push(innerJSON);
}
}
console.log(results);
Now, if we were to read the contents of the data variable from you CSV, using FileReader object would do the trick. You could implement it like this.
var data = "";
var reader = new FileReader();
// I'm assuming you have an input element, whose type is file
// and that you read from it
reader = readAsText(document.getElementById("myFileInput").files[0]);
reader.addEventListener('load',function() {
data = reader.result;
parseData(data);
});
where parseData(data) would be a function doing all of the things shown in the first part of my answer (splitting your CSV into an array, looping, etc). Try it out below.
const file = document.getElementById("file");
const parse = document.getElementById("parse");
var data = "";
parse.addEventListener("click", readFile);
function readFile() {
let reader = new FileReader();
reader.readAsText(file.files[0]);
reader.addEventListener('load', function(e) {
data = reader.result;
parseData(data);
});
}
function parseData(data) {
// Let's remove the quotes first - you don't have to do this
// if it's absolutely necessary to keep them
data = data.replaceAll("\"","");
// Create an array by splitting the CSV by newline
data = data.trim().split(/\r?\n/);
// Extract the first index, since we want to repeat it later on as
// the first key of every JSON element of your resulting array
// This is the long string with the pluses
/* remove this multiline comment to see the contents of data[0]
console.log(data[0]);
*/
var title = data[0].split(",")[0];
// Extract the other main keys - years
var innerKeys = data[1].trim().split(",");
// Remove the the last one, since we don't need it - <HEADER>
innerKeys.pop();
// Prepare the array for our results
var results = [];
// Loop through indivdual rows, and split by comma / ,
// We'll skip the first two, since we've dealt with them
// data[0] being the row with the long string with the pluses
// and data[1] being the row containing the years
for(var i = 2; i < data.length; i++) {
// Let's clean any trailing empty characters
var tempRow = data[i].trim();
// If there's anything we can work with
if(tempRow) {
// Create an array from the values in the current line
tempRow = tempRow.split(",");
// Let's get the value for our first keys
// These are the Total Annual etc strings from your CSV
var tempTitle = tempRow[0];
// Let's declare and fill our temp object
var innerJSON = {};
// The first key is the one with the pluses
// and its value is the Total Annual etc string
innerJSON[title] = tempTitle;
for(var j = 1; j < tempRow.length; j++) {
// Let's fill the years and give them matching values
innerJSON[innerKeys[j]] = tempRow[j];
}
// All done, add it to the resulting array
results.push(innerJSON);
}
}
console.log(results);
}
<input type="file" id="file">
<button type="button" id="parse">Parse</button>
I've an object with keys/value pair where values are comma separated in an array.
e.g.
var myObject = { key1 : [v11, v12, v13, v14] , key2 : [v21, v22, v23, v24]};
What I've working now is keys goes in as column headers and all of the array values in single row. The row data appears as comma separated.
I've a mutator that converts "," to "\n" which then converts row data and still shows in single row , albeit on different lines.
What I need is to display each value from each array on separate row , just like in excel. And also (if possible) merge rows for keys for corresponding value rows.
Here's relevant code
<script>
//converts comma to newline
var customMutator = function(value, data, type, params, component){
return value.join("\n");
}
var tableData = [
{id:1, key:"key1", values: ["v11", "v12", "v13", "v14"]},
{id:1, key:"key2", values: ["v21", "v22", "v23", "v24"]},
];
var table = new
Tabulator("#example-table", {
data:tableData,
columns:[
{title:"key", field:"key", formatter:"textarea"},
{title:"values",field:"values", formatter:"textarea", mutator:customMutator,},
],
});
</script>
Because you are only interested in viewing the data and not editing it, the easiest thing is a custom formatter. (If you need to edit the data in that format, then I am not sure how to do that at this time.)
Your custom formatter will create an element with a border for each item in your value array. This is an example of a custom formatter that you could use.
function customFormatter(cell, formatterParams, onRendered) {
const val = cell.getValue();
const cellDiv = document.createElement('div');
for (let i = 0; i < val.length; i++){
const valItemDiv = document.createElement('div');
valItemDiv.textContent = val[i];
valItemDiv.style.border = 'purple 1px solid';
cellDiv.appendChild(valItemDiv);
}
return cellDiv;
}
Here is a full working example. https://jsfiddle.net/nrayburn/sqdvkm5u/11/
I am trying to avoid the following code because it is too slow:
for (var c = 25; c>2; c--){
if (sheet2.getRange(1,c).getValue() == 0)
{sheet2.deleteColumn(c)}
}
Instead I tried to find a list of columns I want to delete from the array and then set the array. (I recently figure out that deleting rows/columns in a loop is very expensive: google script loop performance)
I found this Removing columns of data in javascript array and try to apply it to my code, but it is not working.
Here is the code.
var ary = sheet2.getRange(2,1,outData.length+1,outData[0].length).getValues();
var indexesToRemove = [];
for (var c = 25; c>2; c--){
if (sheet2.getRange(1,c).getValue() == 0)
{
indexesToRemove.push(c);
}
}
The part above works well. What is not working is the function to remove the columns from the array once I found the indexes to remove. The array _row is not what I am looking for. What am I doing wrong?
removeColumns(ary, indexesToRemove);}
function removeColumns(data, indexes) {
return data.map(function (row) {
// when we remove columns, the indexing gets off by 1 each time, keep track of how many to adjust
var indexAdjustment = 0;
// copy row w/ .slice so we do not modify the original array
var _row = row.slice();
indexes.forEach(function (colIndex) {
// remove column
_row.splice(colIndex - indexAdjustment, 1);
// add 1 to adjustment to account for the column we just removed
indexAdjustment++
});
return _row;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet2 = ss.getSheetByName('Cache');
sheet2.clear();
sheet2.getRange(2,1,_row.length,_row[0].length).setValues(_row);
});
}
BTW, I have also tried this before, but still not working:
var ary = sheet2.getRange(2,1,outData.length+1,outData[0].length).getValues();
for (var c = 25; c>2; c--){
if (sheet2.getRange(1,c).getValue() == 0)
{ ary = ary.map(function(item){
return item.splice(0,c)});
}
}
You want to delete the columns that the value of is 0 in the cells C1:Y1.
You want to reduce the process cost of the script.
You want to achieve this without using Sheets API.
Pattern 1:
In this pattern, at first, the cells which have the value of 0 from the cells C1:Y1 using TextFinder, and the columns are deleted from the retrieved cells using deleteColumn().
Sample script:
const sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange("C1:Y1")
.createTextFinder(0)
.matchEntireCell(true)
.findAll()
.reverse()
.forEach(e => sheet.deleteColumn(e.getColumn()));
Pattern 2:
In this pattern, at first, all values are retrieved from "C1" to the last column for the all data rows, and delete the columns in the array and clear the range, and then, the values are put to the sheet. The method for directly processing the retrieved values has already been proposed. So as other pattern, I proposed the method which uses the transpose.
Sample script:
const sheet = SpreadsheetApp.getActiveSheet();
const range = sheet.getRange(1, 3, sheet.getLastRow(), sheet.getLastColumn() - 2);
const values = range.getValues();
const t = values[0].reduce((ar, r, i) => {
if (r != 0) ar.push(values.map(c => c[i]));
return ar;
}, []);
const v = t[0].map((_, i) => t.map(c => c[i]));
range.clearContent();
sheet.getRange(1, 3, v.length, v[0].length).setValues(v);
Pattern 3:
In this pattern, the request body for the batchUpdate method of Sheets API is created using the 1st row values, and the request body is used for requesting to Sheets API. By this, several columns can be deleted by one API call.
Before you run the script, please enable Sheets API at Advanced Google services.
Sample script:
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getActiveSheet();
const sheetId = sheet.getSheetId();
// Create rerequests for DeleteDimensionRequest.
const requests = sheet.getRange("C1:Y1")
.createTextFinder(0)
.matchEntireCell(true)
.findAll()
.reverse()
.map(e => {
const col = e.getColumn();
return {deleteDimension: {range: {sheetId: sheetId, dimension: "COLUMNS", startIndex: col - 1, endIndex: col}}}
});
// Request to the batchUpdate method using the request body.
Sheets.Spreadsheets.batchUpdate({requests: requests}, spreadsheet.getId());
In this case, requests is created using the method of pattern 1. Each request is as follows. You can see about this structure at the document.
{
"deleteDimension": {
"range": {
"sheetId": "###",
"dimension": "COLUMNS",
"startIndex": ##,
"endIndex": ##
}
}
}
References:
Class TextFinder
Advanced Google services
Method: spreadsheets.batchUpdate
DeleteDimensionRequest
function runOne() {
var d=0;
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var hA=sh.getRange(1,1,1,sh.getLastColumn()).getValues()[0];//header array
var vs=sh.getRange(2,1,sh.getLastRow()-1,sh.getLastColumn()).getValues();//data array
vs.forEach(function(r,j){
var d=0;
hA.forEach(function(h,i){
if(h==0)r.splice(i-d++,1);//removes elements in columns whose headers are == 0
});
});
Logger.log(vs);
}
Try using Sheets Advanced Service and batchUpdate.
Related
Remove all grouped rows / columns in a spreadsheet
Reference
https://developers.google.com/sheets/api/samples/rowcolumn#delete_rows_or_columns
Using Javascript run within a browser using a bookmarklet, my objective is to gather data from links on a web page, then put this into a formatted CSV for download. The tasks as I see them are:
Get the data
Put it into arrays
Format as CSV
Export the data (either as a downloadable file, or load in the browser to save manually)
I have done 1 and 2, giving me an array of arrays as columns for the table. I'm stuck on 3 and 4. Here's a sample of the data:
// test data for 'const' column (length of array will be variable)
var dataColumn = ["tt0468569", "tt0111161", "tt1795369", "tt7738450"];
// making arrays for other columns in export table (most of the other columns will be empty)
var emptyArray = Array(dataColumn.length).fill('')
var titleType = Array(dataColumn.length).fill('Feature Film')
// make array of arrays (columns) ready to export as csv
var dataTable = [emptyArray,dataColumn,emptyArray,emptyArray,emptyArray,emptyArray,titleType,emptyArray,emptyArray,emptyArray,emptyArray,emptyArray,emptyArray,emptyArray,emptyArray,emptyArray];
// column headers for table
var tableHeaders = ["position","const","created","modified","description","Title","Title type","Directors","You rated","IMDb Rating","Runtime (mins)","Year","Genres","Num. Votes","Release Date (month/day/year)","URL"]
And my desired output:
position,const,created,modified,description,Title,Title type,Directors,You rated,IMDb Rating,Runtime (mins),Year,Genres,Num. Votes,Release Date (month/day/year),URL
,tt0468569,,,,,Feature Film,,,,,,,,,
,tt0111161,,,,,Feature Film,,,,,,,,,
,tt1795369,,,,,Feature Film,,,,,,,,,
,tt7738450,,,,,Feature Film,,,,,,,,,
You are almost done on creating the array, just need to change the approach for creating the tableData. Instead of appending the tableData with a set of arrays as empty array and title array you need to map them accordingly.
Take a look at the snippet below:
function downloadExcel() {
var dataColumn = ["tt0468569", "tt0111161", "tt1795369", "tt7738450"];
var tableHeaders = ["position", "const", "created", "modified", "description", "Title", "Title type", "Directors", "You rated", "IMDb Rating", "Runtime (mins)", "Year", "Genres", "Num. Votes", "Release Date (month/day/year)", "URL"];
//now a container for the excel data i.e. tableHeaders+datacreated:
var dataTable = new Array();
dataTable.push(tableHeaders);
//now looping around the data
dataColumn.forEach(function(col) {
dataTable.push(['', col, '', '', '', '', 'Feature Film', '', '', '', '', '', '', '', '', '']);
});
//now converting the array given into a `csv` content
let csvContent = "data:text/csv;charset=utf-8,";
dataTable.forEach(function(rowArray) {
let row = rowArray.join(",");
csvContent += row + "\r\n";
});
//calling the csv download via anchor tag(link) so we can provide a name for the file
var encodedUri = encodeURI(csvContent);
var link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.style.display = 'none';
link.setAttribute("download", "myCSV.csv"); //change it to give your own name
link.innerHTML = "Click Here to download";
document.body.appendChild(link); // Required for FF
link.click();
link.remove(); //removing the link after the download
}
<button onclick="downloadExcel()">Click me to Download excel</button>
One way is - Formatting should just be outputting all the individual lines (TableHeader + all your datatable lines) into an array, then joining the array into 1 long string with an 'end of line' symbol(which may or may not happen automatically, you might need to play with this). Then you put that resultant string into a link/button on your page.
so something along the lines of:
var OutString = [];
// your existing code here for defining the headers
OutString[0] = TableHeader
for (var i = 1; i < LineCount; ++i) { // looping mechanism through pickup from the page
// your existing code here for picking up the details
OutString[i] = dataTable
}
TestFunc += OutString.join(''); // or whatever you want your end of line to be
OutPutLine = '{optional button etc here}';
Then write OutPutLine to your page element.