csv-writer icon indicating copy to clipboard operation
csv-writer copied to clipboard

Wrap string values with new lines in quotes

Open okmanideep opened this issue 1 year ago • 4 comments

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: "[email protected]" }, {phone_number: 8898988989, name: "Bob Marlin", email: "[email protected]"}]
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 
,[email protected]
8898988989,Bob Marlin,[email protected]

okmanideep avatar Jun 14 '24 08:06 okmanideep

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

brakmic avatar Jul 14 '24 17:07 brakmic

@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

okmanideep avatar Jul 16 '24 12:07 okmanideep

@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: "[email protected]" }, 
  {phone_number: 8898988989, name: "Bob Marlin", email: "[email protected]"}
]

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"

brakmic avatar Jul 16 '24 12:07 brakmic

Use createObjectCsvStringifier({ alwaysQuote: true, .. })

fider avatar Dec 10 '24 11:12 fider