Skip to content

Instantly share code, notes, and snippets.

@rudfoss
Created March 26, 2024 10:40

Revisions

  1. rudfoss created this gist Mar 26, 2024.
    67 changes: 67 additions & 0 deletions convertDataToCsvRows.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,67 @@
    const sanitizeCellValue = (value: unknown, cellSeparator: string): string => {
    let valueString = String(value ?? "")
    if (
    valueString.includes(cellSeparator) ||
    valueString.includes(" ") ||
    valueString.includes('"') ||
    valueString.includes("\n")
    ) {
    valueString = valueString.replaceAll('"', '""')
    valueString = `"${valueString}"`
    }
    return valueString
    }

    /**
    * Given a data set will convert it to a csv string with a header and newline-separated rows.
    * @param data The data to convert
    * @param columns The name of each property to include. If not specified will infer properties based on the first row.
    * @param cellSeparator The separator character to use between cells. Defaults to comma.
    * @returns
    */
    export const convertDataToCsvRows = <TRow extends object>(
    data: TRow[],
    columns?: Array<keyof TRow>,
    cellSeparator: "," | ";" | "\t" | " " = ","
    ) => {
    const columnsInOrder = columns ?? (Object.keys(data[0] ?? {}) as Array<keyof TRow>)
    const columnNames = columnsInOrder
    .map((columnName) => sanitizeCellValue(columnName, cellSeparator))
    .join(cellSeparator)

    if (data.length === 0) {
    return [columnNames].join("\n")
    }

    const rows = data.map((row) =>
    columnsInOrder
    .map((columnName) => sanitizeCellValue(row[columnName], cellSeparator))
    .join(cellSeparator)
    )

    return [columnNames, ...rows].join("\n")
    }

    /**
    * Creates a url for downloading the CSV data provided as a file.
    * @param csvData
    * @returns A function that will trigger the download when called.
    */
    export const makeDownloadableCsv = (
    csvData: string,
    filename: string,
    type = "text/csv;charset=utf-8"
    ) => {
    const blob = new Blob([csvData], { type })
    const url = URL.createObjectURL(blob)
    const downloadLink = document.createElement("a")
    downloadLink.href = url
    downloadLink.download = filename
    downloadLink.style.visibility = "hidden"

    return () => {
    document.body.append(downloadLink)
    downloadLink.click()
    downloadLink.remove()
    }
    }