csv-json-core
Version:
Convert CSV to JSON and JSON to CSV using only core Node.js modules. Supports all CSV formats. like both Strings and Files
168 lines (137 loc) • 4.76 kB
JavaScript
const fs = require('fs');
function escapeCsvValue(value) {
const strValue = String(value);
if (strValue.includes(',') || strValue.includes('\n') || strValue.includes('\r') || strValue.includes('"')) {
const escapedValue = strValue.replace(/"/g, '""');
return `"${escapedValue}"`;
}
return strValue;
}
function unescapeCsvValue(value) {
if (value.startsWith('"') && value.endsWith('"')) {
return value.substring(1, value.length - 1).replace(/""/g, '"');
}
return value;
}
function parseCsv(csv) {
const lines = csv.trim().split(/\r?\n/).filter(line => line.trim() !== '');
if (lines.length === 0) {
return [];
}
// Basic header parsing (assuming headers are not quoted or don't contain commas/newlines)
const headers = lines[0].split(',').map(h => h.trim());
const jsonArray = [];
for (let i = 1; i < lines.length; i++) {
const line = lines[i];
const values = [];
let inQuote = false;
let currentField = '';
for (let j = 0; j < line.length; j++) {
const char = line[j];
const nextChar = line[j + 1];
if (char === '"') {
if (inQuote && nextChar === '"') {
currentField += '"';
j++; // Skip the next character as it's part of the escape sequence
} else {
// Toggle inQuote state
inQuote = !inQuote;
}
} else if (char === ',' && !inQuote) {
// End of a field outside of quotes
values.push(unescapeCsvValue(currentField));
currentField = '';
} else {
// Regular character
currentField += char;
}
}
values.push(unescapeCsvValue(currentField));
const rowObject = {};
headers.forEach((header, index) => {
rowObject[header] = values[index] !== undefined ? values[index] : '';
});
jsonArray.push(rowObject);
}
return jsonArray;
}
function buildCsv(jsonArray) {
if (!Array.isArray(jsonArray) || jsonArray.length === 0) {
return '';
}
const allHeaders = new Set();
jsonArray.forEach(item => {
if (typeof item === 'object' && item !== null) {
Object.keys(item).forEach(key => allHeaders.add(key));
}
});
const headers = Array.from(allHeaders);
if (headers.length === 0) {
return '';
}
const headerRow = headers.map(header => escapeCsvValue(header)).join(',');
const rows = jsonArray.map(item => {
return headers.map(header => {
const value = item[header];
return escapeCsvValue(value == null ? '' : value);
}).join(',');
});
return [headerRow, ...rows].join('\n');
}
function csvToJson(input, isFile = false) {
let data;
if (isFile) {
try {
data = fs.readFileSync(input, 'utf8');
} catch (error) {
console.error(`Error reading CSV file "${input}":`, error.message);
throw new Error(`Failed to read CSV file: ${input}. ${error.message}`);
}
} else {
data = input;
}
try {
const jsonArray = parseCsv(data);
return JSON.stringify(jsonArray, null, 2);
} catch (error) {
console.error(`Error parsing CSV data:`, error.message);
throw new Error(`Failed to parse CSV data. ${error.message}`);
}
}
function jsonToCsv(input, isFile = false, output = null) {
let data;
if (isFile) {
try {
data = fs.readFileSync(input, 'utf8');
} catch (error) {
console.error(`Error reading JSON file "${input}":`, error.message);
throw new Error(`Failed to read JSON file: ${input}. ${error.message}`);
}
} else {
data = input;
}
let jsonArray;
try {
jsonArray = JSON.parse(data);
if (!Array.isArray(jsonArray)) {
throw new Error('JSON data must be an array of objects.');
}
} catch (error) {
console.error(`Error parsing JSON data:`, error.message);
throw new Error(`Failed to parse JSON data: Invalid JSON format or not an array. ${error.message}`);
}
try {
const csv = buildCsv(jsonArray);
if (output) {
fs.writeFileSync(output, csv);
}
return csv;
} catch (error) {
console.error(`Error building CSV or writing to file "${output || 'memory'}"`, error.message);
throw new Error(`Failed to convert JSON to CSV or write output file. ${error.message}`);
}
}
module.exports = {
csvToJson,
jsonToCsv
};