UNPKG

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
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 };