json-data-to-csv
Version:
A TypeScript library to convert array of objects to CSV format with customizable delimiter
115 lines (114 loc) • 4.15 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.JsonToCsvError = void 0;
exports.parse = parse;
/**
* Custom error class for JSON to CSV conversion errors
*/
class JsonToCsvError extends Error {
constructor(message, code) {
super(message);
this.code = code;
this.name = 'JsonToCsvError';
}
}
exports.JsonToCsvError = JsonToCsvError;
/**
* Escapes CSV field values that contain special characters
* @param value - The value to escape
* @param delimiter - The delimiter being used
* @returns Escaped value
*/
function escapeCSVField(value, delimiter) {
// If the value contains delimiter, newline, or double quote, wrap in quotes
if (value.includes(delimiter) ||
value.includes('\n') ||
value.includes('\r') ||
value.includes('"')) {
// Escape any existing double quotes by doubling them
const escapedValue = value.replace(/"/g, '""');
return `"${escapedValue}"`;
}
return value;
}
/**
* Converts a value to a string representation suitable for CSV
* @param value - The value to convert
* @returns String representation of the value
*/
function valueToString(value) {
if (value === null || value === undefined) {
return '';
}
if (typeof value === 'object') {
try {
return JSON.stringify(value);
}
catch (error) {
throw new JsonToCsvError('Failed to serialize object value', 'SERIALIZATION_ERROR');
}
}
return String(value);
}
/**
* Parses an array of objects into CSV format
* @param data - Array of objects to convert to CSV
* @param options - Options for parsing (delimiter)
* @returns CSV string with header row and data rows
*/
function parse(data, options = {}) {
var _a;
// Validate input
if (!Array.isArray(data)) {
throw new JsonToCsvError('Input data must be an array', 'INVALID_INPUT');
}
if (data.length === 0) {
throw new JsonToCsvError('Input data cannot be empty', 'EMPTY_INPUT');
}
// Validate delimiter before applying default
if (options.delimiter !== undefined &&
(typeof options.delimiter !== 'string' || options.delimiter.length === 0)) {
throw new JsonToCsvError('Delimiter must be a non-empty string', 'INVALID_DELIMITER');
}
const delimiter = (_a = options.delimiter) !== null && _a !== void 0 ? _a : ',';
try {
// Extract all unique keys from all objects to create comprehensive headers
const allKeys = new Set();
for (const row of data) {
if (typeof row !== 'object' || row === null || Array.isArray(row)) {
throw new JsonToCsvError('All data items must be objects', 'INVALID_DATA_TYPE');
}
Object.keys(row).forEach((key) => allKeys.add(key));
}
if (allKeys.size === 0) {
throw new JsonToCsvError('No valid properties found in data objects', 'NO_PROPERTIES');
}
const headers = Array.from(allKeys);
// Create CSV header row
const headerRow = headers
.map((header) => escapeCSVField(header, delimiter))
.join(delimiter);
// Create CSV data rows
const dataRows = data.map((row, index) => {
try {
return headers
.map((header) => {
const value = valueToString(row[header]);
return escapeCSVField(value, delimiter);
})
.join(delimiter);
}
catch (error) {
throw new JsonToCsvError(`Error processing row ${index + 1}: ${error instanceof Error ? error.message : 'Unknown error'}`, 'ROW_PROCESSING_ERROR');
}
});
// Combine header and data rows
return [headerRow, ...dataRows].join('\n');
}
catch (error) {
if (error instanceof JsonToCsvError) {
throw error;
}
throw new JsonToCsvError(`Unexpected error during parsing: ${error instanceof Error ? error.message : 'Unknown error'}`, 'UNEXPECTED_ERROR');
}
}