Skip to content
LogoLogo

@portable/csv

Functions

Csv.decode

Decodes CSV bytes or text into rows. A row is a string[], or an object keyed by column name when header is set. Values stay strings.

function decode<
  const header extends DecoderStream.Header = false,
  relax extends boolean = false,
>(
  input: string | Uint8Array,
  options: decode.Options<header, relax> = {},
): DecoderStream.Row<header, relax>[];
OptionTypeDefaultDescription
delimiterstring','Field separator, one character. Use '\t' for TSV.
encodingstring'utf-8'Text encoding of byte input, as a TextDecoder label.
headerheaderfalseEmit each row as an object keyed by column name.
maxLengthnumberInfinityLongest allowed row, in UTF-16 code units of input text, not counting the line break.
quotestring | false'"'Quote character, one character, or false to read quotes as ordinary text.
relaxColumnCountrelaxfalseAccept rows whose field count differs from the first row's or the header's.
skipEmptybooleantrueDrop lines with no characters instead of reading each as a row with one empty field.

Throws

  • InvalidInputError When text follows a closing quote, a row's field count differs from the first row's (unless relaxColumnCount is set), or the header repeats a name.
  • TruncatedError When the input ends inside a quoted field.
  • MaxLengthError When a row is longer than maxLength.
import * as Csv from '@portable/csv';
 
Csv.decode('name,age\r\n"Smith, J",42\r\n'); // [['name', 'age'], ['Smith, J', '42']]
Csv.decode('name,age\nAda,36\n', { header: true }); // [{ name: 'Ada', age: '36' }]
Csv.decode('a\tb\n', { delimiter: '\t' }); // [['a', 'b']]

Csv.encode

Encodes rows as CSV text. A field is quoted only when it holds the delimiter, the quote or a line break, and quotes inside it are doubled. Every row, including the last, ends with newline.

function encode(
  rows: readonly encode.Row[],
  options: encode.Options = {},
): string;
OptionTypeDefaultDescription
bombooleanfalseStart the output with a byte order mark, which Excel needs to read UTF-8.
delimiterstring','Field separator, one character. Use '\t' for TSV.
escapeFormulasbooleanfalsePrefix fields that start with =, +, -, @, tab or CR with ' and quote them, so spreadsheets don't run them as formulas.
headerboolean | readonly string[]falseWrite a header row. true takes the names from the first object row's keys. An array gives the names and the order in which object rows' values are written. Without a header row, object rows are written in the first row's key order.
newline"\r\n" | "\n"'\r\n'Line break written after every row.
quotestring | false'"'Quote character, or false to never quote, which throws on a field that needs quoting.

Throws

  • InvalidInputError When quote is false and a field needs quoting, a value isn't an encode.Value or is an invalid Date, or an object row follows array rows and header isn't an array.
  • TypeError When header is true with array rows.
  • RangeError When delimiter, quote or newline isn't allowed.
import * as Csv from '@portable/csv';
 
Csv.encode([
  ['name', 'note'],
  ['Ada', 'said "hi", left'],
]); // 'name,note\r\nAda,"said ""hi"", left"\r\n'
Csv.encode([{ name: 'Ada', age: 36 }], { header: true }); // 'name,age\r\nAda,36\r\n'

Classes

Csv.DecoderStream

A TransformStream that decodes CSV bytes or text into rows. A row is a string[], or an object keyed by column name when header is set. Quoted fields may hold delimiters, doubled quotes and line breaks. Rows end in CRLF, LF or CR, and a leading byte order mark is dropped. The output is the same however the input is split into chunks.

class DecoderStream extends TransformStream<
  Uint8Array | string,
  DecoderStream.Row<header, relax>
> {
  constructor(options: DecoderStream.Options<header, relax> = {});
}
OptionTypeDefaultDescription
delimiterstring','Field separator, one character. Use '\t' for TSV.
encodingstring'utf-8'Text encoding of byte input, as a TextDecoder label.
headerheaderfalseEmit each row as an object keyed by column name.
maxLengthnumberInfinityLongest allowed row, in UTF-16 code units of input text, not counting the line break.
quotestring | false'"'Quote character, one character, or false to read quotes as ordinary text.
relaxColumnCountrelaxfalseAccept rows whose field count differs from the first row's or the header's.
skipEmptybooleantrueDrop lines with no characters instead of reading each as a row with one empty field.
signalAbortSignalErrors the stream with signal.reason when the signal aborts, which makes pipeThrough cancel the source.
import * as Csv from '@portable/csv';
import * as Streams from '@portable/streams';
 
const response = await fetch('https://example.com/users.csv');
const rows = response.body!.pipeThrough(
  new Csv.DecoderStream({ header: true }),
);
for await (const row of Streams.toAsyncIterable(rows)) {
  console.log(row.email);
}

Csv.EncoderStream

A TransformStream that encodes each row as one line of CSV text, as Csv.encode does, and emits it as UTF-8 bytes.

class EncoderStream extends TransformStream<
  encode.Row,
  Uint8Array<ArrayBuffer>
> {
  constructor(options: EncoderStream.Options = {});
}
OptionTypeDefaultDescription
bombooleanfalseStart the output with a byte order mark, which Excel needs to read UTF-8.
delimiterstring','Field separator, one character. Use '\t' for TSV.
escapeFormulasbooleanfalsePrefix fields that start with =, +, -, @, tab or CR with ' and quote them, so spreadsheets don't run them as formulas.
headerboolean | readonly string[]falseWrite a header row. true takes the names from the first object row's keys. An array gives the names and the order in which object rows' values are written. Without a header row, object rows are written in the first row's key order.
newline"\r\n" | "\n"'\r\n'Line break written after every row.
quotestring | false'"'Quote character, or false to never quote, which throws on a field that needs quoting.
signalAbortSignalErrors the stream with signal.reason when the signal aborts, which makes pipeThrough cancel the source.
import * as Csv from '@portable/csv';
import * as Streams from '@portable/streams';
 
const body = Streams.fromIterable(users).pipeThrough(
  new Csv.EncoderStream({ header: ['id', 'email'] }),
);
return new Response(body, { headers: { 'content-type': 'text/csv' } });

Errors

Csv.InvalidInputError

Thrown for malformed CSV: text between a closing quote and the next delimiter or line break, a row whose field count differs from the first row's, or a repeated header name.

class InvalidInputError extends Errors.InvalidInputError {
  readonly kind: string;
}
import * as Csv from '@portable/csv';
 
try {
  Csv.decode('a,b\n1,2,3\n');
} catch (error) {
  if (error instanceof Csv.InvalidInputError) {
    console.log(error.message);
  } // 'Row 2 has 3 fields, but row 1 has 2.'
}

Csv.MaxLengthError

Thrown when a row is longer than maxLength characters.

class MaxLengthError extends Errors.MaxLengthError {
  constructor(options: Errors.MaxLengthError.Options);
  readonly kind: string;
}
import * as Csv from '@portable/csv';
 
try {
  Csv.decode('a,b\nfar too long,x\n', { maxLength: 8 });
} catch (error) {
  if (error instanceof Csv.MaxLengthError) {
    console.log(error.maxLength);
  } // 8
}

Csv.TruncatedError

Thrown when the input ends inside a quoted field.

class TruncatedError extends Errors.TruncatedError {
  readonly kind: string;
}
import * as Csv from '@portable/csv';
 
try {
  Csv.decode('a,"b\n');
} catch (error) {
  if (error instanceof Csv.TruncatedError) {
    console.log('unclosed quote');
  }
}