# Correctness of a CSV writer in Node.js

A friend of mine wanted an array-to-CSV string function for Node.js, so I came up with this. Basically, it can take in a single object, a 2D array or an array of objects.

If the children or parent is an object then the object properties become the headers, if the children are arrays then the first array is the headers.

Each cell is enclosed inside double quotes in the case of commas inside of the cell, and any double quotes inside the cell are escaped. Is there anything else I should be escaping?

exports.objectToCSVString = function (ob) {
var str = "", row, a, i, o, c, r;
if(ob instanceof Object && !(ob instanceof Array)){
ob = [ob];
}
if(ob instanceof Array){
for(r in ob){
if(ob.hasOwnProperty(r)){
row = ob[r];
if(row instanceof Array){
a = "";
for(i in row){
if(row.hasOwnProperty(i)){
a += a.length === 0 ? "" : ",";
a += "\"" + row[i].toString().replaceAll("\"", "\\\"") + "\"";
}
}
str += str.length === 0 ? a : "\r\n" + a;
}else if(row instanceof Object){
if(o === undefined){
o = [];
a = "";
for(c in row){
if(row.hasOwnProperty(c)){
o.push(c);
a += a.length === 0 ? "" : ",";
a += "\"" + c.toString().replaceAll("\"", "\\\"") + "\"";
}
}
str += str.length === 0 ? a : "\r\n" + a;
}
a = "";
for(c in o){
if(o.hasOwnProperty(c) && row[o[c]] !== undefined){
a += a.length === 0 ? "" : ",";
a += "\"" + row[o[c]].toString().replaceAll("\"", "\\\"") + "\"";
}
}
str += str.length === 0 ? a : "\r\n" + a;
}else{
throw "row is not an Array or object";
}
}
}
return str;
}
throw "Object is not an Array";
};

String.prototype.replaceAll = function(a, b){
var t = this, o = [], i;
//get all the occurances of it
for(i = 0; i < t.length; i+=1){
if(t.substr(i, i - 1 + a.length) == a){
o.push(i);
}
}
for(i = 0; i < o.length; i+=1){
t = t.substr(0, o[i] - 1) + b + t.substr(o[i] + b.length, t.length - 1);
}
return t;
};


Is the concept right? Do I cover everything when it comes to creating a CSV string? Any other comments?

Is there any way I can improve this or improve the performance? It seems to be rather quick when running, but I'm not sure what to base it on either.

Examples

Given I have required the file and it is names csv

csv.objectToCSVString({a:"a1",b:"b1"});

will output:

"a", "b"
"a1", "b1"


csv.objectToCSVString([{a:"a1",b:"b1"},{a:"a2",b:"b2"}]);

will output:

"a","b"
"a1","b1"
"a2","b2"


csv.objectToCSVString([["a", "b"], ["a1", "b1"], ["a2", "b2"]]);

will output:

"a","b"
"a1","b1"
"a2","b2"


csv.objectToCSVString({a:"a\"1\"",b:"b\"1\""});

will output:

"a","b"
"a\"1\"","b\"1\""


NOTE I am working on the replace all function, it doesn't work as it should yet

There's a few points you can improve on:

## throw:

throw "Object is not an Array";


You should use throw new Error instead of throw as throw new Error contains a stack trace as well.

## for in:

for in is a bit weird and can cause issues as it iterates over prototypes as well.

Considering you're testing that the properties are array items anyway, you can just use forEach instead.

if(ob instanceof Array){
for(r in ob){
if(ob.hasOwnProperty(r)){
row = ob[r];


into:

if(ob instanceof Array){
ob.forEach(function(row, index){
//...


## Declaring globals:

You shouldn't be declaring globals like:

var str = "", row, a, i, o, c, r;


Use them at their respective levels, don't have left over variables.

• Similarly to how it doesn't really belong in your question, it doesn't belong in my answer: You can just find a replaceAll implementation on MDN. – Quill Jan 3 '16 at 16:15
• I should really post an update on this, I have really progressed since then. :). Thanks for the comments. – FabianCook Jan 4 '16 at 10:51