Migration Guide: PapaParse to jtcsv
Current version: 3.1.0
This guide helps you migrate from PapaParse to jtcsv. Both libraries handle CSV parsing and generation, but jtcsv offers additional security features, streaming support, and TypeScript-first design.
Table of Contents
- Why Migrate?
- Installation
- Quick Comparison
- API Mapping
- Feature Comparison
- Common Migration Patterns
- Breaking Changes to Watch
Why Migrate?
| Feature | PapaParse | jtcsv |
|---|---|---|
| CSV Injection Protection | Manual | Built-in (default) |
| TypeScript Support | @types package | Native |
| Streaming | Basic | Full Node.js streams |
| NDJSON Support | No | Yes |
| TSV Support | Via delimiter | Native API |
| RFC 4180 Compliance | Partial | Full |
| Security Validation | No | Path traversal, Schema validation |
| Bundle Size | ~47KB | ~55KB |
| Zero Dependencies (core) | No | Yes |
Installation
# Remove PapaParse
npm uninstall papaparse @types/papaparse
# Install jtcsv
npm install jtcsvQuick Comparison
PapaParse
import Papa from 'papaparse';
// Parse CSV
const result = Papa.parse(csvString, { header: true });
const data = result.data;
// Generate CSV
const csv = Papa.unparse(jsonData);jtcsv
import { csvToJson, jsonToCsv } from 'jtcsv';
// Parse CSV
const data = csvToJson(csvString);
// Generate CSV
const csv = jsonToCsv(jsonData);API Mapping
Parsing CSV
Basic Parsing
PapaParse:
const result = Papa.parse(csvString, {
header: true,
skipEmptyLines: true,
dynamicTyping: true
});
const data = result.data;
const errors = result.errors;jtcsv:
import { csvToJson } from 'jtcsv';
const data = csvToJson(csvString, {
hasHeaders: true, // equivalent to header: true
trim: true, // trims whitespace, skips empty
parseNumbers: true, // equivalent to dynamicTyping for numbers
parseBooleans: true // parse "true"/"false" as booleans
});Parsing with Custom Delimiter
PapaParse:
Papa.parse(csvString, { delimiter: ';' });jtcsv:
csvToJson(csvString, { delimiter: ';' });
// Or let jtcsv auto-detect (default behavior)
csvToJson(csvString, { autoDetect: true });Parsing File
PapaParse:
Papa.parse(file, {
header: true,
complete: (results) => {
console.log(results.data);
}
});jtcsv:
import { readCsvAsJson } from 'jtcsv';
// Async/await
const data = await readCsvAsJson('./data.csv');
// Sync version available
import { readCsvAsJsonSync } from 'jtcsv';
const data = readCsvAsJsonSync('./data.csv');Row Limit
PapaParse:
Papa.parse(csvString, { preview: 100 }); // First 100 rowsjtcsv:
csvToJson(csvString, { maxRows: 100 });Converting to CSV
Basic Conversion
PapaParse:
const csv = Papa.unparse(data, {
quotes: true,
delimiter: ','
});jtcsv:
import { jsonToCsv } from 'jtcsv';
const csv = jsonToCsv(data, {
delimiter: ',',
rfc4180Compliant: true // Handles quoting automatically
});Custom Headers
PapaParse:
Papa.unparse(data, {
columns: ['name', 'email', 'age']
});jtcsv:
jsonToCsv(data, {
template: { name: '', email: '', age: '' } // Guarantees order
});Header Renaming
PapaParse:
// Not built-in, requires manual transformation
const renamed = data.map(row => ({
'Full Name': row.name,
'Email Address': row.email
}));
Papa.unparse(renamed);jtcsv:
jsonToCsv(data, {
renameMap: {
name: 'Full Name',
email: 'Email Address'
}
});Saving to File
PapaParse:
const csv = Papa.unparse(data);
fs.writeFileSync('output.csv', csv);jtcsv:
import { saveAsCsv } from 'jtcsv';
await saveAsCsv(data, 'output.csv', {
validatePath: true // Security: prevents path traversal
});Streaming
Stream Parsing
PapaParse:
Papa.parse(fs.createReadStream('large.csv'), {
header: true,
step: (row) => {
console.log('Row:', row.data);
},
complete: () => {
console.log('Done');
}
});jtcsv:
import { createCsvToJsonStream } from 'jtcsv';
import { createReadStream } from 'fs';
const csvStream = createCsvToJsonStream({ hasHeaders: true });
createReadStream('large.csv')
.pipe(csvStream)
.on('data', (row) => {
console.log('Row:', row);
})
.on('end', () => {
console.log('Done');
});
// Or use async iterator
import { csvToJsonIterator } from 'jtcsv';
for await (const row of csvToJsonIterator(csvString)) {
console.log('Row:', row);
}Stream Generation
PapaParse:
// No native streaming support for generationjtcsv:
import { createJsonToCsvStream, streamJsonToCsv } from 'jtcsv';
// Using transform stream
const csvTransform = createJsonToCsvStream({ delimiter: ',' });
jsonReadable.pipe(csvTransform).pipe(fileWritable);
// Or direct streaming
await streamJsonToCsv(inputStream, outputStream, { delimiter: ',' });Configuration Options
| PapaParse Option | jtcsv Equivalent | Notes |
|---|---|---|
delimiter | delimiter | Same |
header | hasHeaders | Same concept |
dynamicTyping | parseNumbers, parseBooleans | Split into specific options |
skipEmptyLines | trim | trim: true handles empty lines |
preview | maxRows | Same concept |
step | Use streaming API | createCsvToJsonStream() |
complete | Promise resolution | Async/await pattern |
error | try/catch | Error classes: ParsingError, etc. |
quotes | rfc4180Compliant | Auto-quoting with RFC 4180 |
quoteChar | N/A | Always uses standard " |
escapeChar | N/A | RFC 4180 standard escaping |
columns | template | Object defines order |
newline | N/A | Auto-detected on parse, CRLF on generate |
Feature Comparison
Error Handling
PapaParse:
const result = Papa.parse(csv);
if (result.errors.length > 0) {
result.errors.forEach(err => console.error(err));
}jtcsv:
import { csvToJson, ParsingError, ValidationError } from 'jtcsv';
try {
const data = csvToJson(csv);
} catch (error) {
if (error instanceof ParsingError) {
console.error(`Line ${error.lineNumber}: ${error.message}`);
} else if (error instanceof ValidationError) {
console.error('Validation failed:', error.message);
}
}CSV Injection Protection
PapaParse:
// Manual protection required
const sanitize = (value) => {
if (typeof value === 'string' && /^[=+\-@\t\r]/.test(value)) {
return `'${value}`;
}
return value;
};
const safeData = data.map(row =>
Object.fromEntries(
Object.entries(row).map(([k, v]) => [k, sanitize(v)])
)
);jtcsv:
// Built-in, enabled by default
jsonToCsv(data, { preventCsvInjection: true }); // default
// Disable if needed (not recommended)
jsonToCsv(data, { preventCsvInjection: false });NDJSON Support
PapaParse:
// Not supportedjtcsv:
import { jsonToNdjson, ndjsonToJson, parseNdjsonStream } from 'jtcsv';
// Convert JSON array to NDJSON
const ndjson = jsonToNdjson(data);
// Parse NDJSON string
const data = ndjsonToJson(ndjsonString);
// Stream NDJSON
for await (const obj of parseNdjsonStream(stream)) {
process(obj);
}TSV Support
PapaParse:
Papa.parse(tsvString, { delimiter: '\t' });jtcsv:
import { tsvToJson, jsonToTsv, validateTsv } from 'jtcsv';
// Native TSV API
const data = tsvToJson(tsvString);
const tsv = jsonToTsv(data);
// Validation
const result = validateTsv(tsvString);
if (!result.valid) {
console.error(result.errors);
}Common Migration Patterns
Pattern 1: Simple Parse and Generate
Before (PapaParse):
import Papa from 'papaparse';
function processCSV(input) {
const parsed = Papa.parse(input, { header: true });
const processed = parsed.data.map(transform);
return Papa.unparse(processed);
}After (jtcsv):
import { csvToJson, jsonToCsv } from 'jtcsv';
function processCSV(input) {
const parsed = csvToJson(input);
const processed = parsed.map(transform);
return jsonToCsv(processed);
}Pattern 2: File Processing
Before (PapaParse):
import Papa from 'papaparse';
import fs from 'fs';
async function convertFile(inputPath, outputPath) {
const content = fs.readFileSync(inputPath, 'utf-8');
const { data } = Papa.parse(content, { header: true });
const csv = Papa.unparse(data);
fs.writeFileSync(outputPath, csv);
}After (jtcsv):
import { readCsvAsJson, saveAsCsv } from 'jtcsv';
async function convertFile(inputPath, outputPath) {
const data = await readCsvAsJson(inputPath);
await saveAsCsv(data, outputPath);
}Pattern 3: Streaming Large Files
Before (PapaParse):
let results = [];
Papa.parse(fs.createReadStream('large.csv'), {
header: true,
step: (row) => results.push(row.data),
complete: () => processResults(results)
});After (jtcsv):
import { createCsvFileToJsonStream } from 'jtcsv';
const stream = await createCsvFileToJsonStream('large.csv');
const results = [];
for await (const row of stream) {
results.push(row);
}
processResults(results);
// Or with proper streaming (memory-efficient)
import { streamCsvToJson, createJsonCollectorStream } from 'jtcsv';
const collector = createJsonCollectorStream();
await streamCsvToJson(inputStream, collector);
const results = collector.data;Pattern 4: Browser Usage
Before (PapaParse):
<script src="papaparse.min.js"></script>
<script>
Papa.parse(csvString, { header: true });
</script>After (jtcsv):
<script src="node_modules/jtcsv/dist/jtcsv.umd.js"></script>
<script>
const { csvToJson, jsonToCsv } = jtcsv;
const data = csvToJson(csvString);
</script>
<!-- Or with ES modules -->
<script type="module">
import { csvToJson } from 'jtcsv/browser';
const data = csvToJson(csvString);
</script>Breaking Changes to Watch
1. Return Value Structure
PapaParse returns an object with data, errors, and meta:
const result = Papa.parse(csv);
// result = { data: [...], errors: [...], meta: {...} }jtcsv returns data directly (errors are thrown):
const data = csvToJson(csv);
// data = [...]2. Error Handling
PapaParse collects errors in result object:
if (result.errors.length) { /* handle */ }jtcsv throws typed exceptions:
try {
csvToJson(csv);
} catch (e) {
if (e instanceof ParsingError) { /* handle */ }
}3. Default Delimiter
PapaParse defaults to , (comma) jtcsv auto-detects delimiter by default, or uses ; (semicolon) if specified
// To match PapaParse behavior:
csvToJson(csv, { delimiter: ',', autoDetect: false });4. Dynamic Typing
PapaParse has single dynamicTyping option:
Papa.parse(csv, { dynamicTyping: true });jtcsv splits this into explicit options:
csvToJson(csv, {
parseNumbers: true, // "123" -> 123
parseBooleans: true // "true" -> true
});5. Streaming Callbacks vs Streams
PapaParse uses callbacks:
Papa.parse(stream, {
step: (row) => {},
complete: () => {}
});jtcsv uses Node.js streams and async iterators:
const transform = createCsvToJsonStream();
inputStream.pipe(transform).pipe(outputStream);
// Or async iteration
for await (const row of csvToJsonIterator(csv)) {}