toCsv function
Builds CSV text (RFC 4180): fields are quoted when needed, lines end with CRLF. Prepend '\uFEFF' so that Excel reads UTF-8.
ts
import { toCsv } from 'utils';Signature
ts
export function toCsv(rows: Iterable<readonly unknown[]>, separator = ',', shouldEscapeFormulas = true): stringParameters
| Name | Description |
|---|---|
rows | The rows, header first; null and undefined give empty fields, dates their ISO text, objects their JSON. |
separator | The field separator, such as ';' for a locale with a decimal comma. Defaults to ','. |
shouldEscapeFormulas | Whether to prefix a text starting with =, +, - or @ with ', so that a spreadsheet does not run it. Defaults to true. |
Returns
The CSV text, without a final line break.
Example
ts
toCsv([['time', 'speed'], [new Date(0), 12.5]]); // 'time,speed\r\n1970-01-01T00:00:00.000Z,12.5'Tests
Line coverage: 100 %.
- ✅ joins fields and lines
- ✅ writes field 0 as a,b
- ✅ writes field 1 as say "hi"
- ✅ writes field 2 as two lines
- ✅ writes field 3 as padded
- ✅ writes field 4 as null
- ✅ writes field 5 as Symbol(s)
- ✅ writes field 6 as { a: 1 }
- ✅ writes field 7 as 10n
- ✅ writes field 8 as undefined
- ✅ writes field 9 as true
- ✅ uses the separator
- ✅ neutralizes formulas on demand
- ✅ returns an empty string without row
Source
src/string/to-csv.ts (on GitHub)
Source code
ts
const NEEDS_QUOTES = /[\n\r"]|^\s|\s$/v;
const FORMULA_START = /^[\t\r+\-=@]/v;
function toText(value: unknown): string {
if (typeof value === 'string') {
return value;
}
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
return String(value);
}
if (value instanceof Date) {
return value.toISOString();
}
return typeof value === 'object' && value !== null ? JSON.stringify(value) : '';
}
function formatField(value: unknown, separator: string, shouldEscapeFormulas: boolean): string {
let text = toText(value);
if (shouldEscapeFormulas && typeof value === 'string' && FORMULA_START.test(text)) {
text = `'${text}`;
}
return text.includes(separator) || NEEDS_QUOTES.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
}
/**
* Builds CSV text (RFC 4180): fields are quoted when needed, lines end with CRLF. Prepend `'\uFEFF'` so that
* Excel reads UTF-8.
*
* @param rows - The rows, header first; `null` and `undefined` give empty fields, dates their ISO text,
* objects their JSON.
* @param separator - The field separator, such as `';'` for a locale with a decimal comma. Defaults to `','`.
* @param shouldEscapeFormulas - Whether to prefix a text starting with `=`, `+`, `-` or `@` with `'`, so that a
* spreadsheet does not run it. Defaults to `true`.
* @returns The CSV text, without a final line break.
* @example
* toCsv([['time', 'speed'], [new Date(0), 12.5]]); // 'time,speed\r\n1970-01-01T00:00:00.000Z,12.5'
*/
export function toCsv(rows: Iterable<readonly unknown[]>, separator = ',', shouldEscapeFormulas = true): string {
const lines: string[] = Array.from(rows, (row) =>
row.map((value) => formatField(value, separator, shouldEscapeFormulas)).join(separator),
);
return lines.join('\r\n');
}Test file
ts
import { toCsv } from './to-csv';
describe(toCsv, () => {
it('joins fields and lines', () => {
expect(
toCsv(
[
['time', 'speed'],
[new Date(Date.UTC(2026, 0, 15, 12)), 12.5],
],
',',
false,
),
).toBe('time,speed\r\n2026-01-15T12:00:00.000Z,12.5');
});
it.for([
['a,b', '"a,b"'],
['say "hi"', '"say ""hi"""'],
['two\nlines', '"two\nlines"'],
[' padded', '" padded"'],
[null, ''],
[Symbol('s'), ''],
[{ a: 1 }, '"{""a"":1}"'],
[10n, '10'],
[undefined, ''],
[true, 'true'],
] as const)('writes field %# as %s', ([value, expected]) => {
expect(toCsv([[value]], ',', false)).toBe(expected);
});
it('uses the separator', () => {
expect(toCsv([['1,5', 2]], ';', false)).toBe('1,5;2');
expect(toCsv([['a;b']], ';', false)).toBe('"a;b"');
});
it('neutralizes formulas on demand', () => {
expect(toCsv([['=SUM(A1)', '-3', -3, 'ok']], ',', true)).toBe("'=SUM(A1),'-3,-3,ok");
expect(toCsv([['=SUM(A1)']], ',', false)).toBe('=SUM(A1)');
});
it('returns an empty string without row', () => {
expect(toCsv([], ',', false)).toBe('');
});
});Keywords: to, csv, strings, function