54

I've followed this post How to export JavaScript array info to csv (on client side)? to get a nested js array written as a csv file.

The array looks like:

var test_array = [["name1", 2, 3], ["name2", 4, 5], ["name3", 6, 7], ["name4", 8, 9], ["name5", 10, 11]];

The code given in the link works nicely except that after the third line of the csv file all the rest of the values are on the same line e.g.

name1,2,3
name2,4,5
name3,6,7
name4,8,9name5,10,11 etc etc

Can anyone shed any light on why this is? Same using Chrome or FF.

Thanks

EDIT

jsfiddle http://jsfiddle.net/iaingallagher/dJKz6/

Iain

Community
  • 1
  • 1
duff
  • 705
  • 1
  • 7
  • 7

9 Answers9

91

The cited answer was wrong. You had to change

csvContent += index < infoArray.length ? dataString+ "\n" : dataString;

to

csvContent += dataString + "\n";

As to why the cited answer was wrong (funny it has been accepted!): index, the second parameter of the forEach callback function, is the index in the looped-upon array, and it makes no sense to compare this to the size of infoArray, which is an item of said array (which happens to be an array too).

EDIT

Six years have passed now since I wrote this answer. Many things have changed, including browsers. The following was part of the answer:

START of aged part

BTW, the cited code is suboptimal. You should avoid to repeatedly append to a string. You should append to an array instead, and do an array.join("\n") at the end. Like this:

var lineArray = [];
data.forEach(function (infoArray, index) {
    var line = infoArray.join(",");
    lineArray.push(index == 0 ? "data:text/csv;charset=utf-8," + line : line);
});
var csvContent = lineArray.join("\n");

END of aged part

(Keep in mind that the CSV case is a bit different from generic string concatenation, since for every string you also have to add the separator.)

Anyway, the above seems not to be true anymore, at least not for Chrome and Firefox (it seems to still be true for Safari, though).

To put an end to uncertainty, I wrote a jsPerf test that tests whether, in order to concatenate strings in a comma-separated way, it's faster to push them onto an array and join the array, or to concatenate them first with the comma, and then directly with the result string using the += operator.

Please follow the link and run the test, so that we have enough data to be able to talk about facts instead of opinions.

Walter Tross
  • 10,629
  • 2
  • 31
  • 59
  • Thanks Walter Tross. I'm new self taught in all this so there are plenty of gaps in my knowledge - you just filled several. – duff Sep 17 '13 at 12:48
  • 8
    Are you certain that string += is suboptimal? [This article](http://www.sitepoint.com/javascript-fast-string-concatenation/) seems to say otherwise. – Paul Sep 19 '13 at 10:54
  • @Paul, you are right, I was not up to date with the latest IE versions, which apparently caught up with other browsers on this. Results on jsperf suggest that it's still better to do the join(), though, especially considering that Chrome is now the leader from the performance point of view. – Walter Tross Sep 19 '13 at 15:13
  • 5
    What about the fname? the files is just named Download, and its not a .csv file – Bidstrup Oct 30 '13 at 12:43
  • I'm losing special characters like â and ô because of encodeURI I think :( – Timmetje Apr 02 '15 at 09:25
  • 14
    If there is a '\n' or a ',' in one of the value, it will break. – Micka Oct 20 '15 at 14:30
  • Gentlemen, I'm trying to use this script to download around 1 million lines. The script goes well while creating the array. But when I try to reduce the array (`.join`), even with 150k entries it takes forever. In fact, I couldn't find a way until now. Any tips? – Gabriel L. Oliveira Mar 15 '16 at 11:53
  • @GabrielL.Oliveira I have updated my answer in order to avoid the last concatenation. Most important: try different browsers! – Walter Tross Mar 15 '16 at 12:03
  • 3
    @WalterTross Perfect! Thank you! That worked flawlessly! In addition, I went with link[1] to download the content. Basically using `Blob` and `URL`. [1] http://stackoverflow.com/a/24922761/334872 – Gabriel L. Oliveira Mar 15 '16 at 13:45
  • csvContent = csvContentArray.join(""); var blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }) var link = document.createElement("a"); var url = URL.createObjectURL(blob); link.setAttribute("href", url); link.setAttribute("download", 'data.csv'); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); – Gabriel L. Oliveira Mar 15 '16 at 13:45
  • 1
    And, using this solution, I removed the first entry of csvContentArray ("assigning data:text......."), as it's not necessary anymore. – Gabriel L. Oliveira Mar 15 '16 at 13:46
  • is it possible to have a cell a text in bold? – gabrielAnzaldo Oct 12 '17 at 19:36
  • I want to download for suggesting that you should use arrays to concatenate strings. [It's not faster](https://stackoverflow.com/a/4717855/227299) – Juan Mendes Sep 27 '19 at 19:28
26

General form is:

var ids = []; <= this is your array/collection
var csv = ids.join(",");

For your case you will have to adapt a little bit

Mircea Stanciu
  • 3,194
  • 2
  • 30
  • 34
  • 6
    This answer assumes that there are no "special" characters in the array to be exported (no comma, no quotation mark). If there are, this answer won't work. – ShuberFu Dec 18 '18 at 18:17
24

for a simple csv one map() and a join() are enough:

var csv = test_array.map(function(d){
    return d.join();
}).join('\n');

/* Results in 
name1,2,3
name2,4,5
name3,6,7
name4,8,9
name5,10,11

This method also allows you to specify column separator other than a comma in the inner join. for example a tab: d.join('\t')

On the other hand if you want to do it properly and enclose strings in quotes "", then you can use some JSON magic:

var csv = test_array.map(function(d){
   return JSON.stringify(d);
})
.join('\n') 
.replace(/(^\[)|(\]$)/mg, ''); // remove opening [ and closing ] brackets from each line 

/* would produce
"name1",2,3
"name2",4,5
"name3",6,7
"name4",8,9
"name5",10,11

if you have array of objects like :

var data = [
  {"title": "Book title 1", "author": "Name1 Surname1"},
  {"title": "Book title 2", "author": "Name2 Surname2"},
  {"title": "Book title 3", "author": "Name3 Surname3"},
  {"title": "Book title 4", "author": "Name4 Surname4"}
];

// use
var csv = data.map(function(d){
    return JSON.stringify(Object.values(d));
})
.join('\n') 
.replace(/(^\[)|(\]$)/mg, '');
Konstantin
  • 2,976
  • 18
  • 22
16

I created this code for creating a nice, readable csv files:

var objectToCSVRow = function(dataObject) {
    var dataArray = new Array;
    for (var o in dataObject) {
        var innerValue = dataObject[o]===null?'':dataObject[o].toString();
        var result = innerValue.replace(/"/g, '""');
        result = '"' + result + '"';
        dataArray.push(result);
    }
    return dataArray.join(' ') + '\r\n';
}

var exportToCSV = function(arrayOfObjects) {

    if (!arrayOfObjects.length) {
        return;
    }

    var csvContent = "data:text/csv;charset=utf-8,";

    // headers
    csvContent += objectToCSVRow(Object.keys(arrayOfObjects[0]));

    arrayOfObjects.forEach(function(item){
        csvContent += objectToCSVRow(item);
    }); 

    var encodedUri = encodeURI(csvContent);
    var link = document.createElement("a");
    link.setAttribute("href", encodedUri);
    link.setAttribute("download", "customers.csv");
    document.body.appendChild(link); // Required for FF
    link.click();
    document.body.removeChild(link); 
}

In your case, since you use arrays in array instead of objects in array, You will skip the header part, but you could add the column names yourself by putting this instead of that part:

// headers
csvContent += '"Column name 1" "Column name 2" "Column name 3"\n';

The secret is that a space separates the columns in the csv file, and we put the column values in the double quotes to allow spaces, and escape any double quotes in the values themselves.

Also note that I replace null values with empty string, because that suited my needs, but you can change that and replace it with anything you like.

marc_s
  • 675,133
  • 158
  • 1,253
  • 1,388
Frane Poljak
  • 2,137
  • 19
  • 21
7

If your data contains any newlines or commas, you will need to escape those first:

const escape = text =>
    text.replace(/\\/g, "\\\\")
        .replace(/\n/g, "\\n")
        .replace(/,/g, "\\,")

escaped_array = test_array.map(fields => fields.map(escape))

Then simply do:

csv = escaped_array.map(fields => fields.join(","))
                .join("\n")

If you want to make it downloadable in-browser:

dl = "data:text/csv;charset=utf-8," + csv
window.open(encodeURI(dl))
Zaz
  • 39,637
  • 10
  • 70
  • 92
  • 1
    The escape function is probably not 100% correct, but it should point you in the right direction. – Zaz Nov 17 '16 at 15:13
  • To include a new line (\n) in CSV, you just need to enclose the string, containing new lines, in quotes. To enclose a string in quotes you need to escape the quotes in this string) – Iv Ov Mar 19 '20 at 14:14
5

The following code were written in ES6 and it will work in most of the browsers without an issue.

var test_array = [["name1", 2, 3], ["name2", 4, 5], ["name3", 6, 7], ["name4", 8, 9], ["name5", 10, 11]];

// Construct the comma seperated string
// If a column values contains a comma then surround the column value by double quotes
const csv = test_array.map(row => row.map(item => (typeof item === 'string' && item.indexOf(',') >= 0) ? `"${item}"`: String(item)).join(',')).join('\n');

// Format the CSV string
const data = encodeURI('data:text/csv;charset=utf-8,' + csv);

// Create a virtual Anchor tag
const link = document.createElement('a');
link.setAttribute('href', data);
link.setAttribute('download', 'export.csv');

// Append the Anchor tag in the actual web page or application
document.body.appendChild(link);

// Trigger the click event of the Anchor link
link.click();

// Remove the Anchor link form the web page or application
document.body.removeChild(link);
B.Balamanigandan
  • 3,915
  • 5
  • 55
  • 107
  • 1
    If data has # character it fails. Instead of encodeUri, encodeUriComponent solves the problem with # character. ` encodeURIComponent('a#b') "a%23b" encodeURI('a#b') "a#b" ` – Mehmet Aydoğdu Dec 01 '19 at 16:13
0

The selected answer is probably correct but it seems needlessly unclear.

I found Shomz's Fiddle to be very helpful, but again, needlessly unclear. (Edit: I now see that that Fiddle is based on the OP's Fiddle.)

Here's my version (which I've created a Fiddle for) which I think is more clear:

function downloadableCSV(rows) {
  var content = "data:text/csv;charset=utf-8,";

  rows.forEach(function(row, index) {
    content += row.join(",") + "\n";
  });

  return encodeURI(content);
}

var rows = [
  ["name1", 2, 3],
  ["name2", 4, 5],
  ["name3", 6, 7],
  ["name4", 8, 9],
  ["name5", 10, 11]
];

$("#download").click(function() {
  window.open(downloadableCSV(rows));
});
Jason Swett
  • 38,405
  • 60
  • 193
  • 322
-1
const escapeString = item => (typeof item === 'string') ? `"${item}"` : String(item)

const arrayToCsv = (arr, seperator = ';') => arr.map(escapeString).join(seperator)

const rowKeysToCsv = (row, seperator = ';') => arrayToCsv(Object.keys(row))

const rowToCsv = (row, seperator = ';') => arrayToCsv(Object.values(row))

const rowsToCsv = (arr, seperator = ';') => arr.map(row => rowToCsv(row, seperator)).join('\n')

const collectionToCsvWithHeading = (arr, seperator = ';') => `${rowKeysToCsv(arr[0], seperator)}\n${rowsToCsv(arr, seperator)}`


// Usage: 

collectionToCsvWithHeading([
  { title: 't', number: 2 },
  { title: 't', number: 1 }
])

// Outputs: 
"title";"number"
"t";2
"t";1
Jelle Hak
  • 119
  • 1
  • 5
-1

ES6:

let csv = test_array.map(row=>row.join(',')).join('\n') 
//test_array being your 2D array
Michael Seltenreich
  • 2,376
  • 1
  • 21
  • 47