How to Build a Simple CSV Parser with Lightning Web Components
Create an LWC to upload a CSV File in Salesforce and show its Contents in a Lightning Datatable
Introduction
In Salesforce projects, you frequently have to allow Excel lists or CSV files to be uploaded and create data records based on their contents.
The use of third-party libraries is then frequently considered in these situations. External libraries, on the other hand, are difficult to use in Salesforce because they can only be used as static resources and lack reasonable versioning.
Notwithstanding, contingent upon the intricacy of the necessities, composing a CSV parser with Vanilla JavaScript with little effort is likewise conceivable. Therefore, that is exactly what we intend to do in this post: simply construct our own LWC CSV parser.
Uploading a CSV File
We’ll start with the most important, providing the file upload to be able to make our CSV file available for parsing its contents. Fortunately, Salesforce provides a standard component that we can use for this purpose in our LWC - Lightning Input. So let’s just add an input field of type “file” to the HTML template of our LWC to handle incoming file uploads. We configure the upload to only accept CSV files.
<template>
<lightning-card title="CSV To Datatable" icon-name="doctype:csv">
<div class="slds-p-around_medium">
<lightning-input type="file" label="Please upload a UTF-8 encoded, comma separated .csv file" accept=".csv"
onchange={handleCSVUpload}>
</lightning-input>
</div>
<div style="height: 500px">
<lightning-datatable key-field="Id" data={data} columns={columns} hide-checkbox-column>
</lightning-datatable>
</div>
</lightning-card>
</template>
In the JS part of our LWC we will then use the FileReader object from the File API to read CSV uploads and make them available for further processing as follows:
import { LightningElement } from 'lwc';
export default class CreateCSVTotable extends LightningElement {
columns =[];
data = [];
handleCSVUpload(event) {
const files = event.detail.files;
//console.log("#### files = "+JSON.stringify(files));
if (files.length > 0) {
const file = files[0];
// start reading the uploaded csv file
this.read(file);
}
}
async read(file) {
try {
const result = await this.load(file);
//console.log("#### result = "+JSON.stringify(result));
// execute the logic for parsing the uploaded csv file
this.parseCSV(result);
} catch (e) {
this.error = e;
}
}
async load(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
//console.log("#### reader.result = "+JSON.stringify(reader.result));
resolve(reader.result);
};
reader.onerror = () => {
//console.log("#### reader.error = "+JSON.stringify(reader.error));
reject(reader.error);
};
//console.log("#### file = "+JSON.stringify(file));
reader.readAsText(file);
});
}
parseCSV(csv) {
// parse the csv file and treat each line as one item of an array
const lines = csv.split(/\r\n|\n/);
//console.log("#### lines = "+JSON.stringify(lines));
// parse the first line containing the csv column headers
const headers = lines[0].split(',');
console.log("#### headers = "+JSON.stringify(headers));
// iterate through csv headers and transform them to column format supported by the datatable
this.columns = headers.map((header) => {
return { label: header, fieldName: header };
});
//console.log("#### this.columns = "+JSON.stringify(this.columns));
const data = [];
// iterate through csv file rows and transform them to format supported by the datatable
lines.forEach((line, i) => {
if (i === 0) return;
const obj = {};
const currentline = line.split(',');
for (let j = 0; j < headers.length; j++) {
obj[headers[j]] = currentline[j];
}
data.push(obj);
});
//console.log("#### data = "+JSON.stringify(data));
// assign the converted csv data for the lightning datatable
this.data = data;
}
}
In most cases, the file upload would also allow for the upload of multiple files. In our use case, however, we only want to allow uploading and reading of a single file. In order to only take into account the first file in the list, we use files[0] in our code.
Building the CSV Parser
Now that we understand how to upload and read a CSV file in general, we can build the parser.
The necessary parsing method will basically just go through the data in our CSV file and turn it into a data structure that our code can use more easily later. We already convert our data into a format that can be easily displayed in a table because we want to show it to the user later. The most crucial steps are described in the comments that follow the code:
parseCSV(csv) {
// parse the csv file and treat each line as one item of an array
const lines = csv.split(/\r\n|\n/);
//console.log("#### lines = "+JSON.stringify(lines));
// parse the first line containing the csv column headers
const headers = lines[0].split(',');
console.log("#### headers = "+JSON.stringify(headers));
// iterate through csv headers and transform them to column format supported by the datatable
this.columns = headers.map((header) => {
return { label: header, fieldName: header };
});
//console.log("#### this.columns = "+JSON.stringify(this.columns));
const data = [];
// iterate through csv file rows and transform them to format supported by the datatable
lines.forEach((line, i) => {
if (i === 0) return;
const obj = {};
const currentline = line.split(',');
for (let j = 0; j < headers.length; j++) {
obj[headers[j]] = currentline[j];
}
data.push(obj);
});
//console.log("#### data = "+JSON.stringify(data));
// assign the converted csv data for the lightning datatable
this.data = data;
}
We have now processed and prepared our data for further action using the aforementioned code. Therefore, their UI representation should come next.
Displaying CSV Contents in a Datatable
In the end, we want to present the user with the CSV processing results. We'll use Lightning Datatable, another standard LWC, for this. Our CSV data were already formatted according to the datatable component's specifications in the preceding section. As a result, the only thing that remains to be done is to incorporate the datatable component into our HTML template and to assign the appropriate values to the attributes of the data and columns. We also simply add a Lightning Card as a wrapper to our component to give it a formatted appearance that is a little bit more professional.
<lightning-datatable key-field="Id" data={data} columns={columns} hide-checkbox-column>
</lightning-datatable>
Finally, our table should now display the uploaded CSV file contents in the following format:
In principle, our component can now be utilized anywhere in our organization. The following XML file would be suitable for use on our LWC's App, Home, and Record Pages:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__RecordPage</target>
</targets>
</LightningComponentBundle>
Conclusion
We have developed a straightforward CSV upload component that permits us to read the uploaded file's contents. A Lightning Datatable was also added to the UI to display the parsing results.
However, in this state, it would not be sufficient for a use case in a real project because it is only the beginning. The appropriate business logic for the actual processing of the data would now need to be added in order for our component to be utilized in such a setting. For instance, by including an Apex class that transforms the contents of the file into actual records for a particular kind of org object.
Additionally, our component only currently supports comma-separated CSV files encoded with UTF-8. Naturally, the delimiter may vary between CSV files. Therefore, it would also be a useful extension of our component if the user could set the separator dynamically, perhaps through an additional input field, in order to create a little bit more flexibility in this area.
On GitHub, you can find the entire source code, - https://github.com/avnishyadav25/CSV-Parser-with-LWC
Thanks.
Keep Reading.
This is not parsing as expected when there's a comma in the data like New Jersey, USA in the same field. It's parsing that comma as well into another column. If you have a solution for this please reply