/* globals CsvFormatError */
"use strict";
const csvToJson = require('./csvToJson');
const { InputValidationError, BrowserApiError } = require('./util/errors');
const StreamProcessor = require('./streamProcessor');
/**
* Browser-friendly CSV to JSON API
* Provides methods for parsing CSV strings and File/Blob objects in browser environments
* Proxies configuration to sync csvToJson instance
* @category 4-Browser
*/
class BrowserApi {
/**
* Constructor initializes proxy to sync csvToJson instance
*/
constructor() {
// reuse the existing csvToJson instance for parsing and configuration
this.csvToJson = csvToJson;
}
/**
* Enable or disable automatic type formatting for values
* @param {boolean} active - Whether to format values by type (default: true)
* @returns {this} For method chaining
*/
formatValueByType(active = true) {
this.csvToJson.formatValueByType(active);
return this;
}
/**
* Enable or disable support for RFC 4180 quoted fields
* @param {boolean} active - Whether to support quoted fields (default: false)
* @returns {this} For method chaining
*/
supportQuotedField(active = false) {
this.csvToJson.supportQuotedField(active);
return this;
}
/**
* Set the field delimiter character
* @param {string} delimiter - Character(s) to use as field separator
* @returns {this} For method chaining
*/
fieldDelimiter(delimiter) {
this.csvToJson.fieldDelimiter(delimiter);
return this;
}
/**
* Configure whitespace handling in header field names
* @param {boolean} active - If true, removes all whitespace; if false, only trims edges (default: false)
* @returns {this} For method chaining
*/
trimHeaderFieldWhiteSpace(active = false) {
this.csvToJson.trimHeaderFieldWhiteSpace(active);
return this;
}
/**
* Set the row index where CSV headers are located
* @param {number} index - Zero-based row index containing headers
* @returns {this} For method chaining
*/
indexHeader(index) {
this.csvToJson.indexHeader(index);
return this;
}
/**
* Configure sub-array parsing for special field values
* @param {string} delimiter - Bracket character (default: '*')
* @param {string} separator - Item separator within brackets (default: ',')
* @returns {this} For method chaining
*/
parseSubArray(delimiter = '*', separator = ',') {
this.csvToJson.parseSubArray(delimiter, separator);
return this;
}
/**
* Set a mapper function to transform each row after conversion
* @param {function(object, number): (object|null)} mapperFn - Function receiving (row, index) that returns transformed row or null to filter
* @returns {this} For method chaining
*/
mapRows(mapperFn) {
this.csvToJson.mapRows(mapperFn);
return this;
}
/**
* Parse a CSV string and return as JSON array of objects
* @param {string} csvString - CSV content as string
* @returns {Array<object>} Array of objects representing CSV rows
* @throws {InputValidationError} If csvString is invalid
* @throws {CsvFormatError} If CSV is malformed
* @example
* const csvToJson = require('convert-csv-to-json');
* const rows = csvToJson.browser.csvStringToJson('name,age\nAlice,30');
* console.log(rows); // [{ name: 'Alice', age: '30' }]
*/
csvStringToJson(csvString) {
if (csvString === undefined || csvString === null) {
throw new InputValidationError(
'csvString',
'string',
`${typeof csvString}`,
'Provide valid CSV content as a string to parse.'
);
}
return this.csvToJson.csvToJson(csvString);
}
/**
* Parse a CSV string and return as stringified JSON
* @param {string} csvString - CSV content as string
* @returns {string} JSON stringified array of objects
* @throws {InputValidationError} If csvString is invalid
* @throws {CsvFormatError} If CSV is malformed
* @example
* const csvToJson = require('convert-csv-to-json');
* const jsonString = csvToJson.browser.csvStringToJsonStringified('name,age\nAlice,30');
* console.log(jsonString);
*/
csvStringToJsonStringified(csvString) {
if (csvString === undefined || csvString === null) {
throw new InputValidationError(
'csvString',
'string',
`${typeof csvString}`,
'Provide valid CSV content as a string to parse.'
);
}
return this.csvToJson.csvStringToJsonStringified(csvString);
}
/**
* Parse a CSV string asynchronously (returns resolved Promise)
* @param {string} csvString - CSV content as string
* @returns {Promise<Array<object>>} Promise resolving to array of objects
* @throws {InputValidationError} If csvString is invalid
* @throws {CsvFormatError} If CSV is malformed
* @example
* const csvToJson = require('convert-csv-to-json');
* const rows = await csvToJson.browser.csvStringToJsonAsync('name,age\nAlice,30');
* console.log(rows);
*/
csvStringToJsonAsync(csvString) {
return Promise.resolve(this.csvStringToJson(csvString));
}
/**
* Parse a CSV string asynchronously and return as stringified JSON
* @param {string} csvString - CSV content as string
* @returns {Promise<string>} Promise resolving to JSON stringified array
* @throws {InputValidationError} If csvString is invalid
* @throws {CsvFormatError} If CSV is malformed
* @example
* const csvToJson = require('convert-csv-to-json');
* const json = await csvToJson.browser.csvStringToJsonStringifiedAsync('name,age\nAlice,30');
* console.log(json);
*/
csvStringToJsonStringifiedAsync(csvString) {
return Promise.resolve(this.csvStringToJsonStringified(csvString));
}
/**
* Parse a browser File or Blob object to JSON array.
* @param {File|Blob} file - File or Blob to read as text
* @param {object} [options] - options: { encoding?: string }
* @returns {Promise<object[]>} Promise resolving to parsed JSON rows
* @example
* const csvToJson = require('convert-csv-to-json');
* const fileInput = document.querySelector('#csvfile').files[0];
* const rows = await csvToJson.browser.parseFile(fileInput);
* console.log(rows);
*/
parseFile(file, options = {}) {
if (!file) {
return Promise.reject(new InputValidationError(
'file',
'File or Blob object',
`${typeof file}`,
'Provide a valid File or Blob object to parse.'
));
}
return new Promise((resolve, reject) => {
if (typeof FileReader === 'undefined') {
reject(BrowserApiError.fileReaderNotAvailable());
return;
}
const reader = new FileReader();
reader.onerror = () => reject(BrowserApiError.parseFileError(
reader.error || new Error('Unknown file reading error')
));
reader.onload = () => {
try {
const text = reader.result;
const result = this.csvToJson.csvToJson(String(text));
resolve(result);
} catch (err) {
reject(BrowserApiError.parseFileError(err));
}
};
// If encoding is provided, pass it to readAsText
if (options.encoding) {
reader.readAsText(file, options.encoding);
} else {
reader.readAsText(file);
}
});
}
/**
* Parse CSV from a browser ReadableStream and return parsed data as JSON array
* Processes data in chunks for memory-efficient handling of large streams
* @param {object} stream - Browser ReadableStream containing CSV data
* @returns {Promise<Array<object>>} Promise resolving to array of objects representing CSV rows
* @throws {InputValidationError} If stream is invalid
* @throws {BrowserApiError} If streaming is not supported or parsing fails
* @example
* const csvToJson = require('convert-csv-to-json');
* const response = await fetch('large-dataset.csv');
* const stream = response.body;
* const data = await csvToJson.browser.getJsonFromStreamAsync(stream);
* console.log(data);
*/
async getJsonFromStreamAsync(stream) {
if (typeof ReadableStream === 'undefined') {
throw BrowserApiError.streamingNotSupported();
}
if (!stream || typeof stream.getReader !== 'function') {
throw new InputValidationError(
'stream',
'ReadableStream',
typeof stream,
'Provide a valid browser ReadableStream.'
);
}
const streamProcessor = new StreamProcessor(this.csvToJson, { isBrowser: true });
return streamProcessor.processStream(stream);
}
/**
* Parse CSV from a File object using streaming for memory-efficient processing
* @param {File} file - File object containing CSV data
* @returns {Promise<Array<object>>} Promise resolving to array of objects representing CSV rows
* @throws {InputValidationError} If file is invalid
* @throws {BrowserApiError} If streaming is not supported or parsing fails
* @example
* const csvToJson = require('convert-csv-to-json');
* const fileInput = document.querySelector('#csvfile').files[0];
* const data = await csvToJson.browser.getJsonFromFileStreamingAsync(fileInput);
* console.log(data);
*/
async getJsonFromFileStreamingAsync(file) {
if (!file || !(file instanceof File)) {
throw new InputValidationError(
'file',
'File object',
typeof file,
'Provide a valid File object.'
);
}
// Check if the file supports streaming
if (typeof file.stream === 'function') {
// Use native streaming if available
const stream = file.stream();
return this.getJsonFromStreamAsync(stream);
} else {
// Fallback to regular file parsing for older browsers
return this.parseFile(file);
}
}
/**
* Parse CSV from a File object using streaming with progress callbacks for large files
* Processes data in chunks to avoid memory issues with large datasets
* @param {File} file - File object containing CSV data
* @param {object} options - Processing options
* @param {function(Array<object>, number, number): void} options.onChunk - Callback for each chunk of processed rows
* @param {function(Array<object>): void} [options.onComplete] - Callback when processing is complete
* @param {function(Error): void} [options.onError] - Callback for errors
* @param {number} [options.chunkSize=1000] - Number of rows per chunk
* @returns {Promise<void>} Promise that resolves when streaming starts
* @throws {InputValidationError} If file or options are invalid
* @example
* const csvToJson = require('convert-csv-to-json');
* const fileInput = document.querySelector('#csvfile').files[0];
*
* await csvToJson.browser.getJsonFromFileStreamingAsyncWithCallback(fileInput, {
* chunkSize: 500,
* onChunk: (rows, processed, total) => {
* console.log(`Processed ${processed}/${total} rows`);
* // Handle chunk of rows here
* },
* onComplete: (allRows) => {
* console.log('Processing complete!');
* },
* onError: (error) => {
* console.error('Error:', error);
* }
* });
*/
async getJsonFromFileStreamingAsyncWithCallback(file, options = {}) {
if (!file || !(file instanceof File)) {
throw new InputValidationError(
'file',
'File object',
typeof file,
'Provide a valid File object.'
);
}
if (!options.onChunk || typeof options.onChunk !== 'function') {
throw new InputValidationError(
'options.onChunk',
'function',
typeof options.onChunk,
'Provide a callback function to handle processed chunks.'
);
}
const chunkSize = options.chunkSize || 1000;
const streamProcessor = new StreamProcessor(this.csvToJson, {
isBrowser: true,
chunkSize,
onChunk: options.onChunk,
onComplete: options.onComplete,
onError: options.onError
});
// Check if the file supports streaming
if (typeof file.stream === 'function') {
// Use native streaming if available
const stream = file.stream();
return streamProcessor.processStreamWithCallbacks(stream);
} else {
// Fallback to regular file parsing for older browsers
return this.parseFileWithCallbacks(file, options);
}
}
/**
* Parse a File object with progress callbacks (fallback for non-streaming browsers)
* @param {File} file - File object to parse
* @param {object} options - Processing options
* @private
*/
async parseFileWithCallbacks(file, options) {
const chunkSize = options.chunkSize || 1000;
const onChunk = options.onChunk;
const onComplete = options.onComplete;
const onError = options.onError;
return new Promise((resolve, reject) => {
if (typeof FileReader === 'undefined') {
const error = BrowserApiError.fileReaderNotAvailable();
if (onError) onError(error);
reject(error);
return;
}
const reader = new FileReader();
reader.onerror = () => {
const error = BrowserApiError.parseFileError(
reader.error || new Error('Unknown file reading error')
);
if (onError) onError(error);
reject(error);
};
reader.onload = () => {
try {
const text = reader.result;
const allRows = this.csvToJson.csvToJson(String(text));
// Process in chunks
let processed = 0;
const total = allRows.length;
const processChunk = () => {
const chunk = allRows.slice(processed, processed + chunkSize);
if (chunk.length > 0) {
onChunk(chunk, processed + chunk.length, total);
processed += chunk.length;
// Use setTimeout to avoid blocking the UI
setTimeout(processChunk, 0);
} else {
if (onComplete) onComplete(allRows);
resolve();
}
};
processChunk();
} catch (err) {
const error = BrowserApiError.parseFileError(err);
if (onError) onError(error);
reject(error);
}
};
reader.readAsText(file);
});
}
}
module.exports = new BrowserApi();
Source