csv

js/format/csv.ts

fino:format/csv - CSV parsing and serialization (RFC 4180 + dialect options).

This module handles comma-separated and delimiter-separated tabular text for data import/export workflows. It supports the RFC 4180 quoting model, custom one-character delimiters and quote characters, optional comments, trimming, header rows, explicit column names, relaxed column counts, and field casting.

parse() returns positional string[][] rows by default. Enable header: true to use the first row as object keys, or pass columns to supply keys explicitly. parseStream() consumes async byte chunks and yields parsed rows without requiring the whole input to be present at once. stringify() serializes arrays or records back to CSV and emits CRLF line endings by default.

CSV is not a single fully-standardized ecosystem. When interoperating with spreadsheets, databases, or data warehouses, match their delimiter, quote, header, encoding, and empty-line conventions explicitly.

import { parse, stringify } from 'fino:format/csv';

const rows = parse('name,age\nAda,36\nGrace,85\n', { header: true });
// [{ name: 'Ada', age: '36' }, { name: 'Grace', age: '85' }]

const out = stringify(rows, { header: true });
import { parse } from 'fino:format/csv';

const rows = parse('a;b\n1;2\n', {
  delimiter: ';',
  header: true,
  cast: true,
});

Useful references: - RFC 4180: https://www.rfc-editor.org/rfc/rfc4180 - W3C CSV on the Web: https://www.w3.org/TR/tabular-data-model/

Classes

class CsvParseError extends ParseError {

Error thrown when CSV input is malformed or violates configured column rules.

CsvParseError extends ParseError, so callers can inspect the inherited offset, line, column, format, and renderable diagnostic context. Dialect option validation, such as multi-character delimiters, throws TypeError before parsing starts instead.

import { CsvParseError, parse } from 'fino:format/csv';

try {
  parse('"unterminated');
} catch (error) {
  if (error instanceof CsvParseError) {
    console.error(error.render());
  }
}

Properties

name

Error name, always 'CsvParseError'.

Useful for distinguishing CSV failures in logs or serialized error reports where instanceof checks are unavailable.

Types

type CsvRow = string[]

Positional CSV row with one entry per parsed field.

Rows are returned by parse() and parseStream() when neither header nor columns is enabled. Fields are strings by default; enabling cast can produce non-string runtime values even though the public row shape remains tuple-like for CSV compatibility.

import type { CsvRow } from 'fino:format/csv';
import { parse } from 'fino:format/csv';

const rows: CsvRow[] = parse('name,age\nAda,36\n');
const firstName = rows[1]?.[0];

type CsvRecord = Record<string, string>

Object row produced when header: true or columns is enabled.

Each key is taken from the header row or the explicit columns option. Missing fields are filled with an empty string; extra fields are ignored for records unless column-count validation throws first.

import type { CsvRecord } from 'fino:format/csv';
import { parse } from 'fino:format/csv';

const rows: CsvRecord[] = parse('name,age\nAda,36\n', { header: true });
rows[0]?.name;

Interfaces

interface CsvParseOptions {

Options controlling CSV parsing dialect, row shape, and field conversion.

Defaults match common RFC 4180-style CSV: comma delimiter, double-quote quoting, no comment lines, positional rows, no trimming, no empty-line skipping, strict column counts for records, and no casting.

import { parse, type CsvParseOptions } from 'fino:format/csv';

const options: CsvParseOptions = {
  delimiter: ';',
  header: true,
  skipEmptyLines: true,
};
const rows = parse('name;age\nAda;36\n', options);

Properties

delimiter?: string

One-character field delimiter. Defaults to ",".

Multi-character delimiters are rejected with TypeError before parsing.

import { parse } from 'fino:format/csv';

parse('a;b\n1;2\n', { delimiter: ';' });
quote?: string

One-character quote delimiter. Defaults to the double quote character.

The quote character starts and ends quoted fields, and doubled quote characters inside a quoted field become one literal quote.

import { parse } from 'fino:format/csv';

parse("'a','b'\n'one','two'\n", { quote: "'" });
comment?: string

Optional one-character line comment prefix.

Lines whose first character is the comment prefix are skipped. Comments are recognized only at the start of a row, not after fields.

import { parse } from 'fino:format/csv';

parse('# ignored\na,b\n1,2\n', { comment: '#' });
header?: boolean

Use the first parsed row as object keys. Defaults to false.

When enabled, the header row is not returned as data. If columns is also provided, explicit columns take precedence and the first row is treated as data.

import { parse } from 'fino:format/csv';

const records = parse('name,age\nAda,36\n', { header: true });
columns?: string[]

Explicit object keys for each field.

Supplying columns returns records without consuming a header row. Missing fields are assigned ""; by default row lengths must match exactly.

import { parse } from 'fino:format/csv';

parse('Ada,36\n', { columns: ['name', 'age'] });
skipEmptyLines?: boolean

Skip rows that contain exactly one empty field. Defaults to false.

The option is applied after field parsing and optional trimming, so a blank whitespace-only line is skipped only when trim is also enabled.

import { parse } from 'fino:format/csv';

parse('a\n\nb\n', { skipEmptyLines: true });
trim?: boolean

Trim leading and trailing JavaScript whitespace from every field. Defaults to false.

Trimming applies to quoted and unquoted fields after quote processing.

import { parse } from 'fino:format/csv';

parse(' name \n Ada \n', { trim: true });
relaxColumnCount?: boolean

Allow record rows to have a different field count than the header. Defaults to false.

When disabled, record parsing throws CsvParseError on the first mismatch. When enabled, missing fields become "" and extra fields are ignored.

import { parse } from 'fino:format/csv';

parse('a,b\n1\n', { header: true, relaxColumnCount: true });
cast?: boolean | CastFn

Convert fields from strings to typed values. Defaults to false.

true converts "" and "null" to null, booleans to booleans, and numeric-looking values with Number(). A function receives the raw field plus column index and optional header name.

import { parse } from 'fino:format/csv';

parse('name,age\nAda,36\n', {
  header: true,
  cast: (value, ctx) => ctx.header === 'age' ? Number(value) : value,
});

interface CsvStringifyOptions {

Options controlling CSV output dialect and header emission.

Stringification defaults to comma-delimited CSV, double-quote escaping, and CRLF row endings. Empty input returns an empty string.

import { stringify, type CsvStringifyOptions } from 'fino:format/csv';

const options: CsvStringifyOptions = { header: true, lineEnding: '\n' };
stringify([{ name: 'Ada' }], options);

Properties

delimiter?: string

One-character field delimiter. Defaults to ",".

Fields containing the delimiter are quoted automatically. Multi-character delimiters are rejected with TypeError.

import { stringify } from 'fino:format/csv';

stringify([['a', 'b']], { delimiter: ';' });
quote?: string

One-character quote delimiter. Defaults to the double quote character.

Quote characters inside fields are doubled during output.

import { stringify } from 'fino:format/csv';

stringify([["can't", 'stop']], { quote: "'" });
lineEnding?: string

Row separator appended after each emitted row. Defaults to "\r\n".

Use "\n" when generating Unix-style text files.

import { stringify } from 'fino:format/csv';

stringify([['a'], ['b']], { lineEnding: '\n' });
header?: boolean | string[]

Controls header row emission.

For record rows, an array fixes header order and selected fields. true emits the union of record keys in discovery order. false suppresses the header row. For positional rows, only an array emits headers.

import { stringify } from 'fino:format/csv';

stringify([{ b: 2, a: 1 }], { header: ['a', 'b'] });

Functions

function parse(input: string | Uint8Array, options?: CsvParseOptions & { header?: false; columns?: undefined; }): string[][]

function parse(input: string | Uint8Array, options?: CsvParseOptions & {
  header?: false;
  columns?: undefined;
}): string[][]
function parse(input: string | Uint8Array, options: CsvParseOptions & {
  header: true;
}): Record<string, string>[]
function parse(input: string | Uint8Array, options: CsvParseOptions & {
  columns: string[];
}): Record<string, string>[]
function parse(
  input: string | Uint8Array,
  options?: CsvParseOptions
): string[][] | Record<string, string>[]

Parse a CSV string or UTF-8 byte buffer into positional rows or records.

The return shape depends on header and columns: positional string[][] by default, or records when a header source is configured. Malformed quoted fields and strict record column mismatches throw CsvParseError; invalid delimiter or quote options throw TypeError.

import { parse } from 'fino:format/csv';

parse('a,b\n1,2\n', { header: true }); // [{ a: '1', b: '2' }]

async function* parseStream( src: AsyncIterable<Uint8Array>, options: CsvParseOptions = { } ): AsyncIterableIterator<CsvRow | CsvRecord>

Parse a CSV byte stream row-by-row without buffering the entire input.

Chunks from src are accumulated only until a complete row is available. Quoted fields containing embedded newlines are handled across chunk boundaries. When the stream ends with data after the last newline, that remainder is parsed and yielded as a final row. The yielded row shape follows header and columns in the same way as parse(). Unterminated quoted data or column mismatches throw while iterating; invalid delimiter or quote options throw TypeError on the first next() call.

import { parseStream } from 'fino:format/csv';

async function* bytes() {
  yield new TextEncoder().encode('name,age\nAda,');
  yield new TextEncoder().encode('36\n');
}

for await (const row of parseStream(bytes(), { header: true })) {
  console.log(row);
}

function stringify( rows: string[][] | Record<string, unknown>[], options: CsvStringifyOptions = { } ): string

Serialize rows or records to CSV.

Positional rows are emitted as-is. Record rows use an explicit header array when provided or the union of discovered keys otherwise. Fields are converted with String(), missing record values become "", and fields containing the delimiter, quote, or newlines are quoted automatically. Empty input returns "". Throws TypeError if delimiter is not a single character.

import { stringify } from 'fino:format/csv';

stringify([{ a: '1', b: '2' }]); // 'a,b\r\n1,2\r\n'