Cannot read property 'username' of undefined, excel4node cannot read property username - node.js

I have to print some data in excel format using excel4node. Within the data I have objects that contains information which I want to transfer to excel format. But for some reason it shows error: TypeError: Cannot read property 'username' of undefined. Inside the object I have property username and its value.
Here code below:
const wb = new xl.Workbook();
const data = {};
data = {
username: 'name',
department: 'department',
title: 'title',
percentage: 23,
correct: 27,
date: 2021-09-03T16:38:05.107Z
}
const fileName = "Excel.xlsx";
const filePath = path.resolve(__dirname, "../.././public/files/", fileName);
const ws = wb.addWorksheet("Sheet 1");
const style = wb.createStyle({
font: {
color: "FFFFFFFF",
size: 12,
},
});
const form = [
"name",
"name",
"name",
"name",
"name",
"name",
];
for (let i = 0; i < form.length; i++) {
ws.cell(1, i + 1)
.string(form[i])
.style(style);
switch(i) {
case 1:
ws.column(2).setWidth(30);
break;
case 3:
ws.column(4).setWidth(30);
break;
case 4:
ws.column(5).setWidth(30);
break;
case 5:
ws.column(6).setWidth(30);
break;
}
}
for(let i = 1; i <= data.length; i++) {
ws.cell(i + 1, 1).number(i);
ws.cell(i + 1, 2).string(data[i].username);
ws.cell(i + 1, 3).date(data[i].date.toString());
ws.cell(i + 1, 4).string(data[i].department);
ws.cell(i + 1, 5).number(data[i].percentage);
ws.cell(i + 1, 6).number(data[i].correct);
}
wb.write(filePath);

At the bottom of your script, you can see that you are looping over the data.length and attempting to insert it into your cells.
At the top of the page, your data object is not an Array but an object, and indexing an object must be done by the keys and not the index value. This is why it cannot find the username since it's looking for "object.1.username".
Switch your data object to a array if you would like to use index values.
const data = {};
data = {
username: 'name',
department: 'department',
title: 'title',
percentage: 23,
correct: 27,
date: 2021-09-03T16:38:05.107Z
}
now becomes
const data = [{
username: 'name',
department: 'department',
title: 'title',
percentage: 23,
correct: 27,
date: 2021-09-03T16:38:05.107Z
}];

Related

Update object value using spread Operator

How I can update user.quantity = newQuantity and get user object with updated value.
const user = {
name: 'Jony',
Id: 300,
quantity: 25,
};
let newQuantity = user.quantity + 1;

Parsing CSV with common data preceding other data with NodeJS

I'm trying to read in CSV files with nodejs and the code is like below.
fs.createReadStream(file)
.pipe(csv.parse({from_line: 6, columns: true, bom: true}, (err, data) => {
data.forEach((row, i) => {
As I am using from_line parameter, the data starts at line 6 with header.
The issue is that the line #3 has the date which is also used with other data.
What is the best way to resolve this?
Data file looks like below:
Genre: ABC
Date: 2020-01-01, 2020-12-31
Number of Data: 300
No., Code, Name, sales, delivery, return, stock
1, ......
2, ......
Additional question
I have inserted iconv.decodeStream in the second part of function.
How could I apply the same decoder for header read-in process?
fs.createReadStream(file)
.pipe(iconv.decodeStream("utf-8"))
.pipe(csv.parse({from_line: 6, columns: true, bom: true}, (err, data) => {
data.forEach((row, i) => {
I'd suggest reading the header data first, then you can access this data in your processing callback(s), something like the example below:
app.js
// Import the package main module
const csv = require('csv')
const fs = require("fs");
const { promisify } = require('util');
const parse = promisify(csv.parse);
const iconv = require('iconv-lite');
async function readHeaderData(file, iconv) {
let buffer = Buffer.alloc(1024);
const fd = fs.openSync(file)
fs.readSync(fd, buffer);
fs.closeSync(fd);
buffer = await iconv.decode(buffer, "utf-8");
const options = { to_line: 3, delimiter: ':', columns: false, bom: true, trim: true };
const rows = await parse(buffer, options);
// Convert array to object
return Object.fromEntries(rows);
}
async function readFile(file, iconv) {
const header = await readHeaderData(file, iconv);
console.log("readFile: File header:", header);
fs.createReadStream(file)
.pipe(iconv.decodeStream("utf-8"))
.pipe(csv.parse({ from_line: 6, columns: true, bom: true, trim: true }, (err, data) => {
// We now have access to the header data along with the row data in the callback.
data.forEach((row, i) => console.log( { line: i, header, row } ))
}));
}
readFile('stream-with-skip.csv', iconv)
This will give an output like:
readFile: File header: {
Genre: 'ABC',
Date: '2020-01-01, 2020-12-31',
'Number of Data': '300'
}
and
{
line: 0,
header: {
Genre: 'ABC',
Date: '2020-01-01, 2020-12-31',
'Number of Data': '300'
},
row: {
'No.': '1',
Code: 'Code1',
Name: 'Name1',
sales: 'sales1',
delivery: 'delivery1',
return: 'return1',
stock: 'stock1'
}
}
{
line: 1,
header: {
Genre: 'ABC',
Date: '2020-01-01, 2020-12-31',
'Number of Data': '300'
},
row: {
'No.': '2',
Code: 'Code2',
Name: 'Name2',
sales: 'sales2',
delivery: 'delivery2',
return: 'return2',
stock: 'stock2'
}
}
example.csv
Genre: ABC
Date: 2020-01-01, 2020-12-31
Number of Data: 300
No., Code, Name, sales, delivery, return, stock
1, Code1, Name1, sales1, delivery1, return1, stock1
2, Code2, Name2, sales2, delivery2, return2, stock2

How to push data into existing element of the array of objects in nodejs?

I have this array list of objects.
var list = [{
'ID':1,
'name' : 'Vikas Yadav',
'mobile':8095638475,
'sent':false
},
{
'ID':2,
'name' : 'Rajat Shukla',
'mobile':7486903546,
'sent':false
},
{
'ID':3,
'name' : 'Munna Bhaiya',
'mobile':9056284550,
'sent':false
},
{
'ID':4,
'name' : 'Guddu Pandit',
'mobile':7780543209,
'sent':false
},
{
'ID':5,
'name' : 'Srivani Iyer',
'mobile':8880976501,
'sent':false
}];
Now I want to push two more datas in specific element of this array via forLoop as:
var timeAndOTPArray = {
"time" : new Date(),
"OTP": req.params.ran
}
I am retrieving the list data via cookies into one of the route.
Below is the code I am trying to push the element according to the matching condition.
var lists = req.cookies.list;
Object.keys(lists).forEach(function(item) {
if(req.params.ID == lists[item].ID){ //look for match with name
(lists[item]).push(timeAndOTPArray);
newAddedList.push(lists[item]);
console.log(item, lists[item]);
}
});
Perhaps it's not the correct way. Please help!
Wish you a happy and a prosperous Diwali.
Cheers!
You can use findIndex and append to update the object into list like this:
//List only with ID, easier to read the code
var list = [{'ID':1,},{'ID':2,}]
//your object
var timeAndOTPArray = {
"time" : new Date(),
"OTP": "otp"
}
//Index where object with ID == 2 is
var index = list.findIndex(obj => obj.ID == 2);
//Append the 'timeAndOTPArray' properties into the object itself
list[index] = {"time": timeAndOTPArray.time, "OTP":timeAndOTPArray.OTP, ...list[index]}
console.log(list)
I guess this will help
var lists = req.cookies.list;
Object.keys(lists).forEach(function(item) {
if(req.params.ID == lists[item].ID){ //look for match with ID
Object.keys(timeAndOTPArray).forEach(key=>{
lists[item][key]=timeAndOTPArray[key];
})
}
});
Good evening) I can advice you the best option is update with map
const listItems = [
{
ID: 1,
name: 'Vikas Yadav',
mobile: 8095638475,
sent: false,
},
{
ID: 2,
name: 'Rajat Shukla',
mobile: 7486903546,
sent: false,
},
{
ID: 3,
name: 'Munna Bhaiya',
mobile: 9056284550,
sent: false,
},
{
ID: 4,
name: 'Guddu Pandit',
mobile: 7780543209,
sent: false,
},
{
ID: 5,
name: 'Srivani Iyer',
mobile: 8880976501,
sent: false,
},
];
const paramId = 4;
const result = listItems.map((item) => {
if (paramId === item.ID) {
return {
...item,
time: new Date(),
OTP: 'smth',
};
}
return item;
});
console.log('result', result);
for appending, you can do this,
lists[index] = Object.assign(lists[index], timeAndOTPArray);
If you are using es6,
lists[index] = {...lists[index], timeAndOTPArray};
Here lists is an array of objects.
so lists[item] is an object, so you cant push an object to an object.
In your code timeAndOTPArray is an object.
In your lists object, initialize an empty array called timeAndOTPArray
var index = lists.findIndex(function(item){ return item.ID == req.params.ID});
lists[index].timeAndOTPArray.push(timeAndOTPArray);

Insert Unicode text file data into database

I need a solution for getting data out of a text file that is unicode encoded and write this data into an existing database. I am working with node.js
My text file looks like this (whitespaces are shown)
Text file
Column Active Description Number
1 True test test 0000
1 True test1 test1 0001
1 True test2 test2 0002
1 True test3 test3 0003
1 True test4 test4 0004
I need to get the values of column, active, description and number for each row in order to create a new entry in my database with the respective values. My database already exists and has the columns Column, Active, Description and Number. What is the eaisest way to write the data of the text file into my database. I tried a csv parser
const csv = require('csv-streamify');
const fs = require('fs');
const parser = csv({
delimiter: '\t',
columns: true,
});
parser.on('data', (line) => {
console.log(line)
})
fs.createReadStream('test.txt').pipe(parser)
but that only showed me output like this:
{ '��C\u0000o\u0000l\u0000u\u0000m\u0000n ...
How do i get the correct output and what do i have to do to write the data into the database?
I think it has to be something like this:
connection.query('INSERT INTO Table1 SET ...
I really don't know how to continue.
Using 'byline' instead of 'csv-streamify', you can retrieve the content as follows:
const byline = require('byline');
const fs = require('fs');
var stream = byline(fs.createReadStream('test.txt'));
var index = 0;
var headers;
var data = [];
stream.on('data', function(line) {
var currentData;
var entry;
var i;
line = line.toString(); // Convert the buffer stream to a string line
if (index === 0) {
headers = line.split(/[ ]+/);
} else {
currentData = line.split(/[ ]+/);
entry = {};
for (i = 0; i < headers.length; i++) {
entry[headers[i]] = currentData[i];
}
data.push(entry);
}
index++;
});
stream.on("error", function(err) {
console.log(err);
});
stream.on("end", function() {
console.log(data);
console.log("Done");
});
Everything is stored in the 'data' array as follows:
[ { Column: '1',
Active: 'True',
Description: 'test',
Number: 'test' },
{ Column: '1',
Active: 'True',
Description: 'test1',
Number: 'test1' },
{ Column: '1',
Active: 'True',
Description: 'test2',
Number: 'test2' },
{ Column: '1',
Active: 'True',
Description: 'test3',
Number: 'test3' },
{ Column: '1',
Active: 'True',
Description: 'test4',
Number: 'test4' } ]
Then, it shouldn't be too difficult to write to the SQL database.

NodeJS MongoDB Mongoose export nested subdocuments and arrays to XLSX columns

I have query results from MongoDB as an array of documents with nested subdocuments and arrays of subdocuments.
[
{
RecordID: 9000,
RecordType: 'Item',
Location: {
_id: 5d0699326e310a6fde926a08,
LocationName: 'Example Location A'
}
Items: [
{
Title: 'Example Title A',
Format: {
_id: 5d0699326e310a6fde926a01,
FormatName: 'Example Format A'
}
},
{
Title: 'Example Title B',
Format: {
_id: 5d0699326e310a6fde926a01,
FormatName: 'Example Format B'
}
}
],
},
{
RecordID: 9001,
RecordType: 'Item',
Location: {
_id: 5d0699326e310a6fde926a08,
LocationName: 'Example Location C'
},
Items: [
{
Title: 'Example Title C',
Format: {
_id: 5d0699326e310a6fde926a01,
FormatName: 'Example Format C'
}
}
],
}
]
Problem
I need to export the results to XLSX in column order. The XLSX library is working to export the top-level properties (such as RecordID and RecordType) only. I also need to export the nested objects and arrays of objects. Given a list of property names e.g. RecordID, RecordType, Location.LocationName, Items.Title, Items.Format.FormatName the properties must be exported to XLSX columns in the specified order.
Desired result
Here is the desired 'flattened' structure (or something similar) that
I think should be able to convert to XLSX columns.
[
{
'RecordID': 9000,
'RecordType': 'Item',
'Location.LocationName': 'Example Location A',
'Items.Title': 'Example Title A, Example Title B',
'Items.Format.FormatName': 'Example Format A, Example Format B',
},
{
'RecordID': 9001,
'RecordType': 'Item',
'Location.LocationName': 'Example Location C',
'Items.Title': 'Example Title C',
'Items.Format.FormatName': 'Example Format C',
}
]
I am using the XLSX library to convert the query results to XLSX which works for top-level properties only.
const worksheet: XLSX.WorkSheet = XLSX.utils.json_to_sheet(results.data);
const workbook: XLSX.WorkBook = { Sheets: { 'data': worksheet }, SheetNames: ['data'] };
const excelBuffer: any = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
const data: Blob = new Blob([excelBuffer], { type: EXCEL_TYPE });
FileSaver.saveAs(data, new Date().getTime());
POSSIBLE OPTIONS
I am guessing I need to 'flatten' the structure either using aggregation in the query or by performing post-processing when the query is returned.
Option 1: Build the logic in the MongoDB query to flatten the results.
$replaceRoot might work since it is able to "promote an existing embedded document to the top level". Although I am not sure if this will solve the problem exactly, I do not want to modify the documents in place, I just need to flatten the results for exporting.
Here is the MongoDB query I am using to produce the results:
records.find({ '$and': [ { RecordID: { '$gt': 9000 } } ]},
{ skip: 0, limit: 10, projection: { RecordID: 1, RecordType: 1, 'Items.Title': 1, 'Items.Location': 1 }});
Option 2: Iterate and flatten the results on the Node server
This is likely not the most performant option, but might be the easiest if I can't find a way to do so within the MongoDB query.
UPDATE:
I may be able to use MongoDB aggregate $project to 'flatten' the results. For example, this aggregate query effectively 'flattens' the results by 'renaming' the properties. I just need to figure out how to implement the query conditions within the aggregate operation.
db.records.aggregate({
$project: {
RecordID: 1,
RecordType: 1,
Title: '$Items.Title',
Format: '$Items.Format'
}
})
UPDATE 2:
I have abandoned the $project solution because I would need to change the entire API to support aggregation. Also, I would need to find a solution for populate because aggregate does not support it, rather, it uses $lookup which is possible but time consuming because I would need to write the queries dynamically. I am going back to look into how to flatten the object by creating a function to iterate the array of objects recursively.
Below is a solution for transforming the Mongo data on the server via a function flattenObject which recursively flattens nested objects and returns a 'dot-type' key for nested paths.
Note that the snippet below contains a function that renders and editable table to preview, however, the important part you want (download the file), should be triggered when you run the snippet and click the 'Download' button.
const flattenObject = (obj, prefix = '') =>
Object.keys(obj).reduce((acc, k) => {
const pre = prefix.length ? prefix + '.' : '';
if (typeof obj[k] === 'object') Object.assign(acc, flattenObject(obj[k], pre + k));
else acc[pre + k] = obj[k];
return acc;
}, {});
var data = [{
RecordID: 9000,
RecordType: "Item",
Location: {
_id: "5d0699326e310a6fde926a08",
LocationName: "Example Location A"
},
Items: [{
Title: "Example Title A",
Format: {
_id: "5d0699326e310a6fde926a01",
FormatName: "Example Format A"
}
},
{
Title: "Example Title B",
Format: {
_id: "5d0699326e310a6fde926a01",
FormatName: "Example Format B"
}
}
]
},
{
RecordID: 9001,
RecordType: "Item",
Location: {
_id: "5d0699326e310a6fde926a08",
LocationName: "Example Location C"
},
Items: [{
Title: "Example Title C",
Format: {
_id: "5d0699326e310a6fde926a01",
FormatName: "Example Format C"
}
}]
}
];
const EXCEL_MIME_TYPE = `application/vnd.ms-excel`;
const flattened = data.map(e => flattenObject(e));
const ws_default_header = XLSX.utils.json_to_sheet(flattened);
const ws_custom_header = XLSX.utils.json_to_sheet(flattened, {
header: ['Items.Title', 'RecordID', 'RecordType', 'Location.LocationName', 'Items.Format.FormatName']
});
const def_workbook = XLSX.WorkBook = {
Sheets: {
'data': ws_default_header
},
SheetNames: ['data']
}
const custom_workbook = XLSX.WorkBook = {
Sheets: {
'data': ws_custom_header
},
SheetNames: ['data']
}
const def_excelBuffer = XLSX.write(def_workbook, {
bookType: 'xlsx',
type: 'array'
});
const custom_excelBuffer = XLSX.write(custom_workbook, {
bookType: 'xlsx',
type: 'array'
});
const def_blob = new Blob([def_excelBuffer], {
type: EXCEL_MIME_TYPE
});
const custom_blob = new Blob([custom_excelBuffer], {
type: EXCEL_MIME_TYPE
});
const def_button = document.getElementById('dl-def')
/* trigger browser to download file */
def_button.onclick = e => {
e.preventDefault()
saveAs(def_blob, `${new Date().getTime()}.xlsx`);
}
const custom_button = document.getElementById('dl-cus')
/* trigger browser to download file */
custom_button.onclick = e => {
e.preventDefault()
saveAs(custom_blob, `${new Date().getTime()}.xlsx`);
}
/*
render editable table to preview (for SO convenience)
*/
const html_string_default = XLSX.utils.sheet_to_html(ws_default_header, {
id: "data-table",
editable: true
});
const html_string_custom = XLSX.utils.sheet_to_html(ws_custom_header, {
id: "data-table",
editable: true
});
document.getElementById("container").innerHTML = html_string_default;
document.getElementById("container-2").innerHTML = html_string_custom;
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.14.3/xlsx.full.min.js"></script>
<head>
<title>Excel file generation from JSON</title>
<meta charset="utf-8" />
<style>
.xport,
.btn {
display: inline;
text-align: center;
}
a {
text-decoration: none
}
#data-table,
#data-table th,
#data-table td {
border: 1px solid black
}
</style>
</head>
<script>
function render(type, fn, dl) {
var elt = document.getElementById('data-table');
var wb = XLSX.utils.table_to_book(elt, {
sheet: "Sheet JS"
});
return dl ?
XLSX.write(wb, {
bookType: type,
bookSST: true,
type: 'array'
}) :
XLSX.writeFile(wb, fn || ('SheetJSTableExport.' + (type || 'xlsx')));
}
</script>
<div>Default Header</div>
<div id="container"></div>
<br/>
<div>Custom Header</div>
<div id="container-2"></div>
<br/>
<table id="xport"></table>
<button type="button" id="dl-def">Download Default Header Config</button>
<button type="button" id="dl-cus">Download Custom Header Config</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js"></script>
I wrote a function to iterate all object in the results array and create new flattened objects recursively. The flattenObject function shown here is similar to the previous answer and I took additional inspiration from this related answer.
The '_id' properties are specifically excluded from being added to the flattened object, since ObjectIds are still being returned as bson types even though I have the lean() option set.
I still need to figure out how to sort the objects such that they are in the order given e.g. RecordID, RecordType, Items.Title. I believe that might be easiest to achieve by creating a separate function to iterate the flattened results, although not necessarily the most performant. Let me know if anyone has any suggestions on how to achieve the object sorting by a given order or has any improvements to the solution.
const apiCtrl = {};
/**
* Async array iterator
*/
apiCtrl.asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
// Check if a value is an object
const isObject = (val) => {
return typeof val == 'object' && val instanceof Object && !(val instanceof Array);
}
// Check if a value is a date object
const isDateObject = (val) => {
return Object.prototype.toString.call(val) === '[object Date]';
}
/**
* Iterate object properties recursively and flatten all values to top level properties
* #param {object} obj Object to flatten
* #param {string} prefix A string to hold the property name
* #param {string} res A temp object to store the current iteration
* Return a new object with all properties on the top level only
*
*/
const flattenObject = (obj, prefix = '', res = {}) =>
Object.entries(obj).reduce((acc, [key, val]) => {
const k = `${prefix}${key}`
// Skip _ids since they are returned as bson values
if (k.indexOf('_id') === -1) {
// Check if value is an object
if (isObject(val) && !isDateObject(val)) {
flattenObject(val, `${k}.`, acc)
// Check if value is an array
} else if (Array.isArray(val)) {
// Iterate each array value and call function recursively
val.map(element => {
flattenObject(element, `${k}.`, acc);
});
// If value is not an object or an array
} else if (val !== null & val !== 'undefined') {
// Check if property has a value already
if (res[k]) {
// Check for duplicate values
if (typeof res[k] === 'string' && res[k].indexOf(val) === -1) {
// Append value with a separator character at the beginning
res[k] += '; ' + val;
}
} else {
// Set value
res[k] = val;
}
}
}
return acc;
}, res);
/**
* Convert DB query results to an array of flattened objects
* Required to build a format that is exportable to csv, xlsx, etc.
* #param {array} results Results of DB query
* Return a new array of objects with all properties on the top level only
*/
apiCtrl.buildExportColumns = async (results) => {
const data = results.data;
let exportColumns = [];
if (data && data.length > 0) {
try {
// Iterate all records in results data array
await apiCtrl.asyncForEach(data, async (record) => {
// Convert the multi-level object to a flattened object
const flattenedObject = flattenObject(record);
// Push flattened object to array
exportColumns.push(flattenedObject);
});
} catch (e) {
console.error(e);
}
}
return exportColumns;
}

Resources