JavaScript数组转为CSV

70
我已经按照这篇文章如何在客户端将JavaScript数组信息导出到csv?的指示,将一个嵌套的js数组写成了csv文件。
数组长这样:
var test_array = [["name1", 2, 3], ["name2", 4, 5], ["name3", 6, 7], ["name4", 8, 9], ["name5", 10, 11]];

在链接中给出的代码很好,除了在csv文件的第三行后,所有其余值都在同一行上,例如: name1,2,3 name2,4,5 name3,6,7 name4,8,9name5,10,11等等。
有人能解释为什么会这样吗?在Chrome或FF中都是相同的。
谢谢。
编辑: jsfiddle http://jsfiddle.net/iaingallagher/dJKz6/ Iain
10个回答

98
被引用的答案是错误的。你需要进行更改。
csvContent += index < infoArray.length ? dataString+ "\n" : dataString;

csvContent += dataString + "\n";

关于为什么引用的答案是错误的(很有趣,竟然被接受了!):forEach回调函数的第二个参数index是循环数组时的索引,将其与infoArray的大小进行比较没有意义,因为infoArray是该数组的一个项(也恰好是一个数组)。

编辑

现在我写下这篇答案已经过去六年了。许多事情已经发生了变化,包括浏览器。以下是答案的一部分:

开始陈旧的部分

顺便说一下,引用的代码不太优化。您应该避免重复附加字符串。您应该改为附加到一个数组中,并在最后执行array.join("\n")。像这样:

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");

年迈部分结束

需要记住的是,与通用字符串连接不同,CSV案例有一点不同,因为对于每个字符串,您还必须添加分隔符。

无论如何,上面似乎不再成立了,至少对于Chrome和Firefox来说不再成立(尽管似乎对于Safari仍然成立)。

为了消除不确定性,我写了一个jsPerf测试,测试在以逗号分隔的方式连接字符串时,将它们推入数组并连接数组是否更快,还是先使用逗号将它们连接起来,然后直接使用+=运算符将它们与结果字符串连接。

请点击链接运行测试,这样我们就有足够的数据来谈论事实而不是观点。


9
你确定字符串拼接操作中的 += 不是次优选择吗?这篇文章似乎表明相反的观点。文章链接:http://www.sitepoint.com/javascript-fast-string-concatenation/。 - Paul
5
关于文件名,这些文件只被命名为“Download”,而且它不是一个.csv文件。 - Bidstrup
15
如果一个数值中含有 '\n' 或 ',',它就会出错。 - Micka
3
@WalterTross 太好了!谢谢!那个操作非常顺利!另外,我使用链接[1]来下载内容,基本上是使用 BlobURL。 [1] https://dev59.com/LWUp5IYBdhLWcg3w5KvV#24922761 - Gabriel L. Oliveira
1
而且,使用这种解决方案,我删除了csvContentArray的第一个条目(“分配data:text.......”),因为它不再必要。 - Gabriel L. Oliveira
显示剩余8条评论

40

通用格式为:

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

针对您的情况,您需要进行一些调整。


15
假设要导出的数组中没有“特殊”字符(逗号、引号),则此答案适用。如果有这些字符,则此答案将无效。 - ShuberFu

30

对于一个简单的csv文件,使用map()和join()就足够了:

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

此方法还允许您在内部 join 中指定除逗号以外的列分隔符。例如,制表符:d.join('\t')

另一方面,如果您希望正确地将字符串用引号""括起来,则可以使用一些 JSON 魔法:

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

如果您有一个对象数组,例如:
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, '');

1
不起作用。在示例中的第二种格式的数据上进行了测试。 - Sayed
“values” 在某处被定义了吗? - OG Sean
1
@OGSean,'values'是'Object.values'。请参见https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values。我已更新代码示例以提高清晰度。谢谢。 - Konstantin

16

我创建了这个代码来创建漂亮易读的csv文件:

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); 
}
在您的情况下,由于您在数组中使用的是数组而不是对象,因此您将跳过标题部分,但是您可以通过将此放在那个部分来自行添加列名称:

在您的情况下,由于您在数组中使用的是数组而不是对象,因此您将跳过标题部分,但是您可以通过将此放在那个部分来自行添加列名称:

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

秘密在于csv文件中的列之间用空格分隔,我们将列值放在双引号中以允许包含空格,并转义其中的任何双引号。

还要注意,我用空字符串替换了null值,因为那符合我的需求,但您可以更改并替换为任何您喜欢的内容。


9

如果您的数据包含任何换行符或逗号,则需要首先进行转义处理:

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

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

然后只需执行:

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

如果您希望在浏览器中将其下载:

dl = "data:text/csv;charset=utf-8," + csv
window.open(encodeURI(dl))

1
转义函数可能不是100%正确的,但它应该能指引你朝着正确的方向前进。 - Zaz
要在CSV中包含一个新行(\n),你只需要将包含新行的字符串用引号括起来。 为了将字符串括在引号中,你需要在该字符串中转义引号。 - Iv Ov
错误:fields.map 不是一个函数。 - étale-cohomology
@étale-cohomology:看起来你正在使用一个一维数组;相反,你需要一个数组的数组。 - Zaz

8
以下代码是用ES6编写的,它可以在大多数浏览器中毫无问题地工作。

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);


1
如果数据中有#字符,它会失败。使用encodeURIComponent而不是encodeURI可以解决#字符的问题。encodeURIComponent('a#b') "a%23b" encodeURI('a#b') "a#b" - Mehmet Aydoğdu

1
所选答案可能是正确的,但似乎不必要地不清楚。
我发现Shomz's Fiddle非常有帮助,但同样不够清晰。(编辑:我现在看到那个Fiddle是基于OP的Fiddle创建的。)
这是我的版本(我已经为它创建了一个Fiddle),我认为更加清晰:
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));
});

0

ES6:

let csv = test_array.map(row=>row.join(',')).join('\n') 
//test_array being your 2D array

-1
如果您正在使用React并希望在客户端生成CSV文件,只需执行以下操作:
import CsvGenerator from '@exlabs/react-csv-generator';

const data = [{ id: 1, name: 'first' }, { id: 2, name: 'second' }];

const MyComponent = () => {
  return (
    <CsvGenerator fileName="my-name" items={data}>
      Download!
    </CsvGenerator>
  );
};

重要的是,这个包很轻便,支持Excel和Numbers。 链接到npm包

-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

不适用于我。 - étale-cohomology

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接