Deno - Read and Parse CSV File Examples

Need to read and parse a CSV file in Deno? Find out in this tutorial.

Reading and parsing a CSV file can be done easily in Deno. First, you need to load the file contents using built-in functions such as Deno.readTextFile or Deno.open. For parsing the file contents, Deno has a module named csv as a part of std modules. Below are the usage examples which include how to create custom parse functions, customize the column names, set the separator, as well as using BufReader.

Using Deno std csv Module

The function of csv module you need to use is either parse or readMatrix.

Using parse

First, import and re-export the module in deps.ts. That makes it easier for us to manage the dependency version instead of directly importing the module in each file that uses the function.

  import {
    parse as parseCsv
  } from 'https://deno.land/std@0.82.0/encoding/csv.ts';

  export { parseCsv };

For this tutorial, let's assume we have a csv file named items.csv with the following contents.

  Name,Quantity,Price
  One,100,1000
  Two,10,5000
  Three,20,3000

We are going to use parse function which is shown below.

  async function parse(
    input: string | BufReader,
    opt: ParseOptions = {
      skipFirstRow: false,
    },
  )

You are required to pass an input as the first parameter which can be a string or a BufReader. To get the contents of a file as a string, you can use Deno.readTextFile. Because Deno needs to read a file, you need to add --allow read flag while running deno run command. The second parameter is ParseOptions object. ParseOptions has the following fields.

  • skipFirstRow?: boolean: Whether to exclude first row as data. If the value is true and columns option is provided, the first line will be skipped. If the value is true but columns option is not provided, the first line will be used as the header.
  • columns?: string[] | ColumnOptions[]: Allows you to define the list of column names and create a custom parse function for the column.
  • parse?: (input: unknown) => unknown: Custom parse functions for all rows.
  • separator?: string: Character that separates values. Defaults to ,.
  • comment?: string: Character that starts a comment. Defaults to #.
  • trimLeadingSpace?: boolean: Whether to trim the leading space of the value. Defaults to false.
  • lazyQuotes?: boolean: Whether to allow unquoted quote in a quoted field or non double quoted quotes in quoted field. Defaults to false.
  • fieldsPerRecord?: number: The row number to be used as number of column reference. If this value is set, it will check the number of fields on each row.

The ColumnOptions type has two fields:

  • name: string: The column name.
  • parse?: (input: string) => unknown: Parse function for the column.

By default, if you don't pass the second argument, Deno will use the default value which sets skipFirstRow to false. Below is a basic example that reads the file contents as a string first.

  import { parseCsv } from './deps.ts';

  const content = await parseCsv(await Deno.readTextFile('items.csv'));

  console.log(content);

Output:

  [
    [ "Name", "Quantity", "Price" ],
    [ "One", "100", "1000" ],
    [ "Two", "10", "5000" ],
    [ "Three", "20", "3000" ]
  ]

From the above output, you can see that the first row is treated as data by default. To make the first row treated as the header, you can set skipFirstRow option to true. It also changes the return type to a list of objects.

  import { parseCsv } from './deps.ts';

  const content = await parseCsv(await Deno.readTextFile('items.csv'), {
    skipFirstRow: true,
  });

  console.log(content);

Output:

  [
  { Name: "One", Quantity: "100", Price: "1000" },
  { Name: "Two", Quantity: "10", Price: "5000" },
  { Name: "Three", Quantity: "20", Price: "3000" }
]

Set Column Names and Parse Function

If you want to use different column names, you can pass columns option with an array of strings as the value to be used as column names.

  import { parseCsv } from './deps.ts';

  const content = await parseCsv(await Deno.readTextFile('items.csv'), {
    skipFirstRow: true,
    columns: ['item', 'quantity.', 'pricePerItem'],
  });

  console.log(content);

Output:

  [
    { item: "One", "quantity.": "100", pricePerItem: "1000" },
    { item: "Two", "quantity.": "10", pricePerItem: "5000" },
    { item: "Three", "quantity.": "20", pricePerItem: "3000" }
  ]

If you need to use a custom parse function for certain columns, you can pass an array of ColumnOptions objects as columns option instead.

  import { parseCsv } from './deps.ts';

  const content = await parseCsv(await Deno.readTextFile('items.csv'), {
    skipFirstRow: true,
    columns: [
      { name: 'item' },
      { name: 'quantity' },
      {
        name: 'pricePerItem',
        parse: (e: string): string => {
          return `$${e}`;
        },
      },
    ],
  });

  console.log(content);

Output:

  [
    { item: "One", "quantity.": "100", pricePerItem: "1000" },
    { item: "Two", "quantity.": "10", pricePerItem: "5000" },
    { item: "Three", "quantity.": "20", pricePerItem: "3000" }
  ]

The above examples always return the same number of columns according to the csv file. If you define columns option with a different number of columns, you will get the following error.

  error: Uncaught (in promise) Error number of fields line:1

However, it provides a way to return custom data for each row. You can pass a function as parse option. The return value of the function can be any type. If you choose to return an object, the number of columns doesn't have to be the same as the number of columns in the csv file.

  import { parseCsv } from './deps.ts';

  const content = await parseCsv(await Deno.readTextFile('items.csv'), {
    skipFirstRow: true,
    parse: (e: any): unknown => {
      return { item: e.Name, quantity: e.Quantity, pricePerItem: `$${e.Price.toLocaleString('en-US')}`, isExpensive: e.Price >= 3000 };
    },
  });

  console.log(content);
  [
    { item: "One", quantity: "100", pricePerItem: "$1000", isExpensive: false },
    { item: "Two", quantity: "10", pricePerItem: "$5000", isExpensive: true },
    { item: "Three", quantity: "20", pricePerItem: "$3000", isExpensive: true }
  ]

Set Separator

The default separator is comma (,). You can change it by passing separator option.

  const content = await parseCsv(await Deno.readTextFile('items2.csv'), { separator: ';' });

Validate Number of Columns

The parser can also validate the number of columns on each row. To enable the validation, you need to pass fieldsPerRecord option with the number of row to be used as the reference.

  const content = await parseCsv(await Deno.readTextFile('items2.csv'), { fieldsPerRecord: 0 });

If there's a row with a different number of columns, it will throw an error.

  Check file:///home/ivan/Projects/deno/src/examples/csv_read.ts
error: Uncaught (in promise) Error: record on line 4: wrong number of fields
          throw new ParseError(lineIndex, lineIndex, null, ERR_FIELD_COUNT);
              ^
      at readMatrix (csv.ts:325:15)
      at async parse (csv.ts:402:9)
      at async csv_read.ts:12:17

Using BufReader

If the file is very big, it's recommended to pass a BufReader instead of reading the entire file as a string. To create a BufReader from a csv file, you can get an instance of File by using Deno.open. Then pass the FileBufReader

  const file = await Deno.open('items.csv');
  const content = await parseCsv(new BufReader(file));

Using readMatrix

Another function you can use to parse a csv file is readMatrix

  function readMatrix(
    reader: BufReader,
    opt: ReadOptions = {
      separator: ",",
      trimLeadingSpace: false,
      lazyQuotes: false,
    },
  )

The function returns string[][]. You have to pass the input as BufReader as it doesn't accept string. The second parameter is an object of type ReadOptions which has the following fields.

  • separator?: string: Character that separates values. Defaults to ,.
  • comment?: string: Character that starts a comment. Defaults to #.
  • trimLeadingSpace?: boolean: Whether to trim the leading space of the value. Defaults to false.
  • lazyQuotes?: boolean: Whether to allow unquoted quote in a quoted field or non double quoted quotes in quoted field. Defaults to false.
  • fieldsPerRecord?: number: The row number to be used as number of column reference. If this value is set, it will check the number of fields on each row.
  const file = await Deno.open('items.csv');
  const content = await readMatrix(new BufReader(file));

Because readMatrix doesn't have as many options as parse and doesn't allow to have return type other than string[][], using parse is a more popular and recommended choice.

That's how to parse a csv file in Deno.