ryu1kn / csv-writer

Convert objects/arrays into a CSV string or write them into a CSV file
https://www.npmjs.com/package/csv-writer
MIT License
247 stars 39 forks source link

Wrap string values with new lines in quotes #110

Open okmanideep opened 3 months ago

okmanideep commented 3 months ago

Reproduction code

import { createObjectCsvWriter } from 'csv-writer';

const csvPath = 'test.csv'
const csvWriter = createObjectCsvWriter({
path: csvPath,
header: [
    { id: 'phone_number', title: 'phone_number' },
    { id: 'name', title: 'name' },
    { id: 'email', title: 'email' },
],
});
let data = [{ phone_number: 9978789799, name: "John Doe \r", email: "johndoe@email.com" }, {phone_number: 8898988989, name: "Bob Marlin", email: "bormarlin@email.com"}]
await csvWriter.writeRecords(data);

The resultant output is not a valid CSV. Checked with CSV Validator Tool

The output

phone_number,name,email
9978789799,John Doe 
,johndoe@email.com
8898988989,Bob Marlin,bormarlin@email.com
brakmic commented 2 months ago

Hi @okmanideep ,

I’ve been working on my own fork of this project called csv-writer-portable, where I've addressed this exact problem.

First off, the CSV generated by the original version isn't actually invalid. I tested it using the CSV Validator Tool, and it checks out fine. But I totally get that just being valid isn't always enough; we need it to handle things the way we expect.

In csv-writer-portable, I added a feature that lets you pass in a custom function to handle things like new lines. Here's a quick example of how you can use it:

import { createObjectCsvWriter } from 'csv-writer-portable';

const csvPath = 'test.csv';
const csvWriter = createObjectCsvWriter({
  path: csvPath,
  header: [
    { id: 'phone_number', title: 'phone_number' },
    { id: 'name', title: 'name' }
  ],
  filterFunction: (str) => {
    // A simple regex to remove \r and \n chars
    return str.replace(/[\r\n]/g, '');
  },
  alwaysQuote: true
});

const data = [
  { phone_number: 9978789799, name: "John \rDoe\n" },
  { phone_number: 8898988989, name: "Bob Marlin" }
];

async function writeCsv() {
  await csvWriter.writeRecords(data);
}

writeCsv().catch(err => console.error('Error writing CSV:', err));

This way, you can clean up your strings however you need before they get written to the file.

Also, csv-writer-portable supports running in the browser, which might be handy depending on your use case.

Feel free to check it out on npm. Hopefully, it helps you out with your issue!

Cheers,

Harris

okmanideep commented 2 months ago

@brakmic My bad. My repro script was not correct. The issue is visible only if there is another column after the column with \r or \r\n in the value. Updated the repro script. The output is invalid.

Although there is value in a custom filter function for csv-writer, this one in particular seems like something that needs to be handled by default. Will checkout csv-writer-portable

brakmic commented 2 months ago

@okmanideep

I tested it with the latest version of csv-writer-portable.

import { createObjectCsvWriter } from 'csv-writer-portable';

const csvPath = 'test.csv';
const csvWriter = createObjectCsvWriter({
  path: csvPath,
  header: [
    { id: 'phone_number', title: 'phone_number' },
    { id: 'name', title: 'name' }
  ],
  filterFunction: (value: any) => {
    const str = String(value);
    // a simple regex to remove \r and \n chars
    return str.replace(/[\r\n]/g, '');
  },
  alwaysQuote: true
});

let data = [{ phone_number: 9978789799, name: "John Doe \r", email: "johndoe@email.com" }, 
  {phone_number: 8898988989, name: "Bob Marlin", email: "bormarlin@email.com"}
]

async function writeCsv() {
  await csvWriter.writeRecords(data);
}

writeCsv().catch(err => console.error('Error writing CSV:', err));

test.csv output:

"phone_number","name"
"9978789799","John Doe "
"8898988989","Bob Marlin"