Loading CSV files in Node
Introduction #
This project loads a CSV file from the local filesystem and generates a JSON object that’s written to a JSON file, which can be used for sample data in other projects.
Instructions to run this #
-
Clone the project from GitHub
https://github.com/r3bl-alliance/node-csv-to-json
. -
Go to the folder that you cloned the project in, and run
npm install
. -
Then build the code using
npm run build
. -
Finally run it using
npm start
.
There’s a script run.sh
that is provided for your convenience to do the build and run step in one.
What is in this project #
There are a few interesting things in this project that I will call out in this article.
-
Using
ya-csv
to load CSV files from the filesystem. More info here. -
Using
inquirer
to prompt the user for input from the console. This is a good library to build a command line interpreter (CLI). More info here. -
Using Flow type in order to annotate the JavaScript code with static typing that is used while building, but stripped before running the code. More info here.
ya-csv #
This is a really simple to use library to read and write CSV files. Here’s the code to read a CSV file from your local filesystem.
function createObjectFromCSVFile(inputFile: string, onFileLoadedFunction: Array) {
let csv = require("ya-csv")
let returnValue: Array = new Array()
let reader = csv.createCsvFileReader(inputFile, { columnsFromHeader: true })
reader.addListener(
"data",
// Called after each row from CSV file is loaded.
(rowObject) => {
// The rowObject is a JSON object, you can get it's properties
// out by using the keys that are from the first header row of
// the input CSV file.
// Eg: if header row of the CSV file is: Type, Trans Date,
// Post Date then you can use rowObject['Type'],
// rowObject['Trans Date'], and rowObject['Post Date'] to get
// the values for each of the columns in the row.
returnValue.push(rowObject)
}
)
reader.addListener(
"end",
// Called after all rows have been loaded from CSV file.
function (data_row) {
onFileLoadedFunction(reader.columnNames, returnValue)
}
)
}
The ya-csv
library reads each line from your input file and fires an event data
that you attach
a listener function to. If you have 10 rows in your CSV (including the header), then the function
will be run 9 times.
By passing the object {columnsFromHeader: true}
to the createCsvFileReader()
function, you are
telling the ya-csv
parser to use the first line of the file to get all the header names.
csv.createCsvFileReader(inputFile, { columnsFromHeader: true })
Here are a few lines from the sampledata.csv
file (in the assets
folder).
Type,Trans Date,Post Date,Description,Amount,
Sale,06/30/2017,07/02/2017,WHOLEFDS STC 10267,-25.25,
Sale,06/30/2017,07/02/2017,WHOLEFDS LAT 10155,-16.56,
Sale,06/29/2017,06/30/2017,AMAZON MKTPLACE PMTS,-12.99,
Sale,06/29/2017,06/30/2017,Amazon.com,-44.6,
Sale,06/29/2017,07/02/2017,WHOLEFDS STC 10267,-7.5,
The first line is used to get the following column names:
Type
Trans Date
Post Date
Description
Amount
- ``
In the data
listener function of the ya-csv
parser, you can access these fields using the
following syntax: rowObject['Trans Date']
. Note: there are spaces in the column names, which is
why you can’t use the normal rowObject.Trans Date
syntax to get the cell at that column for the
given row.
Finally, if you wanted to write a CSV file using this library, here is some code.
let csv = require("ya-csv")
let fs = require("fs")
let writer = csv.createCsvStreamWriter(fs.createWriteStream("file_name.csv"))
writer.writeRecord(["column1", "column2", "column3"])
Note: this code will rewrite the file everytime you create the writer. If you want to append data to
an existing CSV file instead, then create the writer
object in a different way.
let writer = csv.createCsvStreamWriter(fs.createWriteStream("file_name.csv", { flags: "a" }))
inquirer #
The inquirer
library is a very sophisticated way to get user input. You can present the user with
prompts and get text input, or even checkboxes, radio buttons, etc. all done as text.
For the purposes of this project, the requirement was to get two things from the user: the input file name, and the output file name.
Here’s the code to make this happen.
let inquirer = require("inquirer")
let functions = require("./functions")
// Display welcome message.
let message: string = "node-csv-to-json"
console.log(message)
// Ask the user for the path to the CVS file.
let questions = [
{
type: "input",
name: "inputFilePath",
message: "What is the path to your input CSV file?",
default: "assets/sampledata.csv",
},
{
type: "output",
name: "outputFilePath",
message: "What is the path to the output JSON file?",
default: "assets/sampledata.json",
},
]
inquirer.prompt(questions).then(userReply)
There’s a callback function userReply
that gets called, when the library gets the required
answers. And it passes this function an object that contains the answers.
function userReply(answers: FileLocationsType) {
let inputFile = answers.inputFilePath
let outputFile = answers.outputFilePath
console.log(inputFile)
console.log(outputFile)
}
Flow type #
Facebook’s Flow type is an excellent way to add type safety to JavaScript in a very non invasive way. Flow is great for creating type aliases that describe the shape of objects, so that you can remember what type of object goes where, instead of having to guess.
Here are some great links for Flow and Node.
I’ve also added some npm scripts to make this go easier.
-
npm run build
- This actually stripts out all the flow annotations and copies the JS files to thelib/
folder. -
npm start
- This just runs the index.js file in thelib/
folder (and notsrc
folder).
The way I’ve used Flow in this project is to annotate some of the variables with primitive types
such as string
, and Array
.
I’ve also created my own type alias, in the types.js
file.
type FileLocationsType = {
inputFilePath: string,
outputFilePath: string,
}
Note: I’ve got // @flow
at the top of each JS file that has Flow, to let the pre-processor know
that these are files that the type annotations should be stripped out from.
I import my type alias into the index.js
file using the following statement.
import type FileLocationsType from "./types.js"
Then, I’m able to use the FileLocationsType
in my code, like this.
function userReply(answers: FileLocationsType) {
let inputFile = answers.inputFilePath
let outputFile = answers.outputFilePath
}
👀 Watch Rust 🦀 live coding videos on our YouTube Channel.
📦 Install our useful Rust command line apps usingcargo install r3bl-cmdr
(they are from the r3bl-open-core project):
- 🐱
giti
: run interactive git commands with confidence in your terminal- 🦜
edi
: edit Markdown with style in your terminalgiti in action
edi in action