convert-csv-to-json 3.27.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/csvToJson.js CHANGED
@@ -1,11 +1,19 @@
1
1
  "use strict";
2
2
 
3
- let fileUtils = require("././util/fileUtils");
4
- let stringUtils = require("././util/stringUtils");
5
- let jsonUtils = require("././util/jsonUtils");
6
-
7
- const newLine = /\r?\n/;
8
- const defaultFieldDelimiter = ";";
3
+ const fileUtils = require('./util/fileUtils');
4
+ const stringUtils = require('./util/stringUtils');
5
+ const jsonUtils = require('./util/jsonUtils');
6
+ const {
7
+ ConfigurationError,
8
+ CsvFormatError,
9
+ JsonValidationError
10
+ } = require('./util/errors');
11
+
12
+ const DEFAULT_FIELD_DELIMITER = ",";
13
+ const QUOTE_CHAR = '"';
14
+ const CRLF = '\r\n';
15
+ const LF = '\n';
16
+ const CR = '\r';
9
17
 
10
18
  class CsvToJson {
11
19
 
@@ -30,8 +38,8 @@ class CsvToJson {
30
38
  }
31
39
 
32
40
  indexHeader(indexHeaderValue) {
33
- if(isNaN(indexHeaderValue)){
34
- throw new Error('The index Header must be a Number!');
41
+ if (isNaN(indexHeaderValue)) {
42
+ throw ConfigurationError.invalidHeaderIndex(indexHeaderValue);
35
43
  }
36
44
  this.indexHeaderValue = indexHeaderValue;
37
45
  return this;
@@ -48,6 +56,19 @@ class CsvToJson {
48
56
  return this;
49
57
  }
50
58
 
59
+ /**
60
+ * Sets a mapper function to transform each row after conversion
61
+ * @param {Function} mapperFn - Function that receives (row, index) and returns transformed row or null to filter out
62
+ * @returns {this} - For method chaining
63
+ */
64
+ mapRows(mapperFn) {
65
+ if (typeof mapperFn !== 'function') {
66
+ throw new TypeError('mapperFn must be a function');
67
+ }
68
+ this.rowMapper = mapperFn;
69
+ return this;
70
+ }
71
+
51
72
  generateJsonFileFromCsv(fileInputName, fileOutputName) {
52
73
  let jsonStringified = this.getJsonFromCsvStringified(fileInputName);
53
74
  fileUtils.writeFile(jsonStringified, fileOutputName);
@@ -78,43 +99,142 @@ class CsvToJson {
78
99
 
79
100
  csvToJson(parsedCsv) {
80
101
  this.validateInputConfig();
81
- let lines = parsedCsv.split(newLine);
102
+
103
+ // Parse CSV into individual records, respecting quoted fields that may contain newlines
104
+ let records = this.parseRecords(parsedCsv);
105
+
82
106
  let fieldDelimiter = this.getFieldDelimiter();
83
107
  let index = this.getIndexHeader();
84
108
  let headers;
85
109
 
86
- if(this.isSupportQuotedField){
87
- headers = this.split(lines[index]);
88
- } else {
89
- headers = lines[index].split(fieldDelimiter);
110
+ // Find the header row
111
+ while (index < records.length) {
112
+ if (this.isSupportQuotedField) {
113
+ headers = this.split(records[index]);
114
+ } else {
115
+ headers = records[index].split(fieldDelimiter);
116
+ }
117
+
118
+ if (stringUtils.hasContent(headers)) {
119
+ break;
120
+ }
121
+ index++;
90
122
  }
91
123
 
92
- while(!stringUtils.hasContent(headers) && index <= lines.length){
93
- index = index + 1;
94
- headers = lines[index].split(fieldDelimiter);
124
+ if (!headers) {
125
+ throw CsvFormatError.missingHeader();
95
126
  }
96
127
 
97
128
  let jsonResult = [];
98
- for (let i = (index + 1); i < lines.length; i++) {
129
+ for (let i = (index + 1); i < records.length; i++) {
99
130
  let currentLine;
100
- if(this.isSupportQuotedField){
101
- currentLine = this.split(lines[i]);
102
- }
103
- else{
104
- currentLine = lines[i].split(fieldDelimiter);
131
+ if (this.isSupportQuotedField) {
132
+ currentLine = this.split(records[i]);
133
+ } else {
134
+ currentLine = records[i].split(fieldDelimiter);
105
135
  }
136
+
106
137
  if (stringUtils.hasContent(currentLine)) {
107
- jsonResult.push(this.buildJsonResult(headers, currentLine));
138
+ let row = this.buildJsonResult(headers, currentLine);
139
+
140
+ // Apply row mapper if defined
141
+ if (this.rowMapper) {
142
+ row = this.rowMapper(row, i - (index + 1)); // Pass row and 0-based row index
143
+ // If mapper returns null/undefined, skip this row (allows filtering)
144
+ if (row != null) {
145
+ jsonResult.push(row);
146
+ }
147
+ } else {
148
+ jsonResult.push(row);
149
+ }
108
150
  }
109
- }
151
+ }
110
152
  return jsonResult;
111
153
  }
112
154
 
155
+ /**
156
+ * Parse CSV content into individual records, respecting quoted fields that may span multiple lines.
157
+ * RFC 4180 compliant parsing - handles quoted fields that may contain newlines.
158
+ * @param {string} csvContent - The raw CSV content
159
+ * @returns {string[]} Array of record strings
160
+ */
161
+ parseRecords(csvContent) {
162
+ let records = [];
163
+ let currentRecord = '';
164
+ let insideQuotes = false;
165
+ let i = 0;
166
+
167
+ while (i < csvContent.length) {
168
+ let char = csvContent[i];
169
+
170
+ // Handle quote characters
171
+ if (char === QUOTE_CHAR) {
172
+ if (insideQuotes && i + 1 < csvContent.length && csvContent[i + 1] === QUOTE_CHAR) {
173
+ // Escaped quote: two consecutive quotes = single quote representation
174
+ currentRecord += QUOTE_CHAR + QUOTE_CHAR;
175
+ i += 2;
176
+ } else {
177
+ // Toggle quote state
178
+ insideQuotes = !insideQuotes;
179
+ currentRecord += char;
180
+ i++;
181
+ }
182
+ continue;
183
+ }
184
+
185
+ // Handle line endings (only outside quoted fields)
186
+ if (!insideQuotes) {
187
+ let lineEndingLength = this.getLineEndingLength(csvContent, i);
188
+ if (lineEndingLength > 0) {
189
+ records.push(currentRecord);
190
+ currentRecord = '';
191
+ i += lineEndingLength;
192
+ continue;
193
+ }
194
+ }
195
+
196
+ // Regular character
197
+ currentRecord += char;
198
+ i++;
199
+ }
200
+
201
+ // Add the last record if not empty
202
+ if (currentRecord.length > 0) {
203
+ records.push(currentRecord);
204
+ }
205
+
206
+ // Validate matching quotes
207
+ if (insideQuotes) {
208
+ throw CsvFormatError.mismatchedQuotes('CSV');
209
+ }
210
+
211
+ return records;
212
+ }
213
+
214
+ /**
215
+ * Get the length of line ending at current position (CRLF=2, LF=1, CR=1, or 0)
216
+ * @param {string} content - CSV content
217
+ * @param {number} index - Current index to check
218
+ * @returns {number} Length of line ending (0 if none)
219
+ */
220
+ getLineEndingLength(content, index) {
221
+ if (content.slice(index, index + 2) === CRLF) {
222
+ return 2;
223
+ }
224
+ if (content[index] === LF) {
225
+ return 1;
226
+ }
227
+ if (content[index] === CR && content[index + 1] !== LF) {
228
+ return 1;
229
+ }
230
+ return 0;
231
+ }
232
+
113
233
  getFieldDelimiter() {
114
234
  if (this.delimiter) {
115
235
  return this.delimiter;
116
236
  }
117
- return defaultFieldDelimiter;
237
+ return DEFAULT_FIELD_DELIMITER;
118
238
  }
119
239
 
120
240
  getIndexHeader(){
@@ -168,74 +288,105 @@ class CsvToJson {
168
288
  }
169
289
 
170
290
  validateInputConfig(){
171
- if(this.isSupportQuotedField) {
172
- if(this.getFieldDelimiter() === '"'){
173
- throw new Error('When SupportQuotedFields is enabled you cannot defined the field delimiter as quote -> ["]');
174
- }
175
- if(this.parseSubArraySeparator === '"'){
176
- throw new Error('When SupportQuotedFields is enabled you cannot defined the field parseSubArraySeparator as quote -> ["]');
177
- }
178
- if(this.parseSubArrayDelimiter === '"'){
179
- throw new Error('When SupportQuotedFields is enabled you cannot defined the field parseSubArrayDelimiter as quote -> ["]');
180
- }
181
- }
291
+ if(this.isSupportQuotedField) {
292
+ if(this.getFieldDelimiter() === '"'){
293
+ throw ConfigurationError.quotedFieldConflict('fieldDelimiter', '"');
294
+ }
295
+ if(this.parseSubArraySeparator === '"'){
296
+ throw ConfigurationError.quotedFieldConflict('parseSubArraySeparator', '"');
297
+ }
298
+ if(this.parseSubArrayDelimiter === '"'){
299
+ throw ConfigurationError.quotedFieldConflict('parseSubArrayDelimiter', '"');
300
+ }
301
+ }
182
302
  }
183
303
 
184
304
  hasQuotes(line) {
185
305
  return line.includes('"');
186
306
  }
187
307
 
308
+ /**
309
+ * Split a CSV record line into fields, respecting quoted fields per RFC 4180.
310
+ * Handles:
311
+ * - Quoted fields with embedded delimiters and newlines
312
+ * - Escaped quotes (double quotes within quoted fields)
313
+ * - Empty quoted fields
314
+ * @param {string} line - A single CSV record line
315
+ * @returns {string[]} Array of field values
316
+ */
188
317
  split(line) {
189
- if(line.length == 0){
318
+ if (line.length === 0) {
190
319
  return [];
191
320
  }
192
- let delim = this.getFieldDelimiter();
193
- let subSplits = [''];
194
- if (this.hasQuotes(line)) {
195
- let chars = line.split('');
196
-
197
- let subIndex = 0;
198
- let startQuote = false;
199
- let isDouble = false;
200
- chars.forEach((c, i, arr) => {
201
- if (isDouble) { //when run into double just pop it into current and move on
202
- subSplits[subIndex] += c;
203
- isDouble = false;
204
- return;
205
- }
206
-
207
- if (c != '"' && c != delim ) {
208
- subSplits[subIndex] += c;
209
- } else if(c == delim && startQuote){
210
- subSplits[subIndex] += c;
211
- } else if( c == delim ){
212
- subIndex++
213
- subSplits[subIndex] = '';
214
- return;
215
- } else {
216
- if (arr[i + 1] === '"') {
217
- //Double quote
218
- isDouble = true;
219
- //subSplits[subIndex] += c; //Skip because this is escaped quote
220
- } else {
221
- if (!startQuote) {
222
- startQuote = true;
223
- //subSplits[subIndex] += c; //Skip because we don't want quotes wrapping value
224
- } else {
225
- //end
226
- startQuote = false;
227
- //subSplits[subIndex] += c; //Skip because we don't want quotes wrapping value
228
- }
229
- }
230
- }
231
- });
232
- if(startQuote){
233
- throw new Error('Row contains mismatched quotes!');
321
+
322
+ let fields = [];
323
+ let currentField = '';
324
+ let insideQuotes = false;
325
+ let delimiter = this.getFieldDelimiter();
326
+
327
+ for (let i = 0; i < line.length; i++) {
328
+ let char = line[i];
329
+
330
+ // Handle quote character
331
+ if (char === QUOTE_CHAR) {
332
+ if (this.isEscapedQuote(line, i, insideQuotes)) {
333
+ // Two consecutive quotes inside quoted field = escaped quote
334
+ currentField += QUOTE_CHAR;
335
+ i++; // Skip next quote
336
+ } else if (this.isEmptyQuotedField(line, i, insideQuotes, currentField, delimiter)) {
337
+ // Empty quoted field: "" at field start before delimiter/end
338
+ i++; // Skip closing quote
339
+ } else {
340
+ // Regular quote: toggle quoted state
341
+ insideQuotes = !insideQuotes;
234
342
  }
235
- return subSplits;
236
- } else {
237
- return line.split(delim);
343
+ } else if (char === delimiter && !insideQuotes) {
344
+ // Delimiter outside quotes marks field boundary
345
+ fields.push(currentField);
346
+ currentField = '';
347
+ } else {
348
+ // Regular character (including embedded newlines in quoted fields)
349
+ currentField += char;
350
+ }
351
+ }
352
+
353
+ // Add final field
354
+ fields.push(currentField);
355
+
356
+ // Validate matching quotes
357
+ if (insideQuotes) {
358
+ throw CsvFormatError.mismatchedQuotes('row');
238
359
  }
360
+
361
+ return fields;
362
+ }
363
+
364
+ /**
365
+ * Check if character at index is an escaped quote (double quote)
366
+ * @returns {boolean}
367
+ */
368
+ isEscapedQuote(line, index, insideQuoted) {
369
+ return insideQuoted &&
370
+ index + 1 < line.length &&
371
+ line[index + 1] === QUOTE_CHAR;
372
+ }
373
+
374
+ /**
375
+ * Check if this is an empty quoted field: "" before delimiter or end of line
376
+ * @returns {boolean}
377
+ */
378
+ isEmptyQuotedField(line, index, insideQuoted, currentField, delimiter) {
379
+ if (insideQuoted || currentField !== '' || index + 1 >= line.length) {
380
+ return false;
381
+ }
382
+
383
+ let nextChar = line[index + 1];
384
+ if (nextChar !== QUOTE_CHAR) {
385
+ return false; // Not a quote pair
386
+ }
387
+
388
+ let afterQuotes = index + 2;
389
+ return afterQuotes === line.length || line[afterQuotes] === delimiter;
239
390
  }
240
391
  }
241
392
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  const fileUtils = require('./util/fileUtils');
4
4
  const csvToJson = require('./csvToJson');
5
+ const { InputValidationError } = require('./util/errors');
5
6
 
6
7
  class CsvToJsonAsync {
7
8
  constructor() {
@@ -57,6 +58,14 @@ class CsvToJsonAsync {
57
58
  return this;
58
59
  }
59
60
 
61
+ /**
62
+ * Set row mapper function to transform each row
63
+ */
64
+ mapRows(mapperFn) {
65
+ this.csvToJson.mapRows(mapperFn);
66
+ return this;
67
+ }
68
+
60
69
  /**
61
70
  * Set encoding
62
71
  */
@@ -87,7 +96,12 @@ class CsvToJsonAsync {
87
96
  */
88
97
  async getJsonFromCsvAsync(inputFileNameOrCsv, options = {}) {
89
98
  if (inputFileNameOrCsv === null || inputFileNameOrCsv === undefined) {
90
- throw new Error('inputFileNameOrCsv is not defined!!!');
99
+ throw new InputValidationError(
100
+ 'inputFileNameOrCsv',
101
+ 'string (file path) or CSV string content',
102
+ `${typeof inputFileNameOrCsv}`,
103
+ 'Either provide a valid file path or CSV content as a string.'
104
+ );
91
105
  }
92
106
 
93
107
  if (options.raw) {
@@ -0,0 +1,227 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Custom error classes following clean code principles
5
+ * Provides clear, actionable error messages with context
6
+ */
7
+
8
+ /**
9
+ * Base class for all CSV parsing errors
10
+ * Provides consistent error formatting and context
11
+ */
12
+ class CsvParsingError extends Error {
13
+ constructor(message, code, context = {}) {
14
+ super(message);
15
+ this.name = 'CsvParsingError';
16
+ this.code = code;
17
+ this.context = context;
18
+ Error.captureStackTrace(this, this.constructor);
19
+ }
20
+
21
+ toString() {
22
+ let output = `${this.name}: ${this.message}`;
23
+
24
+ if (this.context && Object.keys(this.context).length > 0) {
25
+ output += '\n\nContext:';
26
+ Object.entries(this.context).forEach(([key, value]) => {
27
+ output += `\n ${key}: ${this.formatValue(value)}`;
28
+ });
29
+ }
30
+
31
+ return output;
32
+ }
33
+
34
+ formatValue(value) {
35
+ if (value === null) return 'null';
36
+ if (value === undefined) return 'undefined';
37
+ if (typeof value === 'string') return `"${value}"`;
38
+ if (typeof value === 'object') return JSON.stringify(value);
39
+ return String(value);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Input validation errors
45
+ */
46
+ class InputValidationError extends CsvParsingError {
47
+ constructor(paramName, expectedType, receivedType, details = '') {
48
+ const message =
49
+ `Invalid input: Parameter '${paramName}' is required.\n` +
50
+ `Expected: ${expectedType}\n` +
51
+ `Received: ${receivedType}${details ? '\n' + details : ''}`;
52
+
53
+ super(message, 'INPUT_VALIDATION_ERROR', {
54
+ parameter: paramName,
55
+ expectedType,
56
+ receivedType
57
+ });
58
+ this.name = 'InputValidationError';
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Configuration-related errors
64
+ */
65
+ class ConfigurationError extends CsvParsingError {
66
+ constructor(message, conflictingOptions = {}) {
67
+ super(message, 'CONFIGURATION_ERROR', conflictingOptions);
68
+ this.name = 'ConfigurationError';
69
+ }
70
+
71
+ static quotedFieldConflict(optionName, value) {
72
+ return new ConfigurationError(
73
+ `Configuration conflict: supportQuotedField() is enabled, but ${optionName} is set to '${value}'.\n` +
74
+ `The quote character (") cannot be used as a field delimiter, separator, or sub-array delimiter when quoted field support is active.\n\n` +
75
+ `Solutions:\n` +
76
+ ` 1. Use a different character for ${optionName} (e.g., '|', '\\t', ';')\n` +
77
+ ` 2. Disable supportQuotedField() if your CSV doesn't contain quoted fields\n` +
78
+ ` 3. Refer to RFC 4180 for proper CSV formatting: https://tools.ietf.org/html/rfc4180`,
79
+ { optionName, value, conflictingOption: 'supportQuotedField' }
80
+ );
81
+ }
82
+
83
+ static invalidHeaderIndex(value) {
84
+ return new ConfigurationError(
85
+ `Invalid configuration: indexHeader() expects a numeric value.\n` +
86
+ `Received: ${typeof value} (${value})\n\n` +
87
+ `Solutions:\n` +
88
+ ` 1. Ensure indexHeader() receives a number: indexHeader(0), indexHeader(1), etc.\n` +
89
+ ` 2. Headers are typically found on row 0 (first line)\n` +
90
+ ` 3. Use indexHeader(2) if headers are on the 3rd line`,
91
+ { parameterName: 'indexHeader', value, type: typeof value }
92
+ );
93
+ }
94
+ }
95
+
96
+ /**
97
+ * CSV parsing errors with detailed context
98
+ */
99
+ class CsvFormatError extends CsvParsingError {
100
+ constructor(message, context = {}) {
101
+ super(message, 'CSV_FORMAT_ERROR', context);
102
+ this.name = 'CsvFormatError';
103
+ }
104
+
105
+ static missingHeader() {
106
+ return new CsvFormatError(
107
+ `CSV parsing error: No header row found.\n` +
108
+ `The CSV file appears to be empty or has no valid header line.\n\n` +
109
+ `Solutions:\n` +
110
+ ` 1. Ensure your CSV file contains at least one row (header row)\n` +
111
+ ` 2. Verify the file is not empty or contains only whitespace\n` +
112
+ ` 3. Check if you need to use indexHeader(n) to specify a non-standard header row\n` +
113
+ ` 4. Refer to RFC 4180 for proper CSV format: https://tools.ietf.org/html/rfc4180`
114
+ );
115
+ }
116
+
117
+ static mismatchedQuotes(location = 'CSV') {
118
+ return new CsvFormatError(
119
+ `CSV parsing error: Mismatched quotes detected in ${location}.\n` +
120
+ `A quoted field was not properly closed with a matching quote character.\n\n` +
121
+ `RFC 4180 rules for quoted fields:\n` +
122
+ ` • Fields containing delimiters or quotes MUST be enclosed in double quotes\n` +
123
+ ` • To include a quote within a quoted field, use two consecutive quotes: ""\n` +
124
+ ` • Example: "Smith, John" (name contains comma)\n` +
125
+ ` • Example: "He said ""Hello""" (text contains quotes)\n\n` +
126
+ `Solutions:\n` +
127
+ ` 1. Review your CSV for properly paired quote characters\n` +
128
+ ` 2. Use double quotes ("") to escape quotes within quoted fields\n` +
129
+ ` 3. Ensure all commas within field values are inside quotes\n` +
130
+ ` 4. Enable supportQuotedField(true) if you're using quoted fields`,
131
+ { location }
132
+ );
133
+ }
134
+ }
135
+
136
+ /**
137
+ * File operation errors
138
+ */
139
+ class FileOperationError extends CsvParsingError {
140
+ constructor(operation, filePath, originalError) {
141
+ const message =
142
+ `File operation error: Failed to ${operation} file.\n` +
143
+ `File path: ${filePath}\n` +
144
+ `Reason: ${originalError.message}\n\n` +
145
+ `Solutions:\n` +
146
+ ` 1. Verify the file path is correct: ${filePath}\n` +
147
+ ` 2. Check file permissions (read access for input, write access for output)\n` +
148
+ ` 3. Ensure the directory exists and is writable for output files\n` +
149
+ ` 4. Verify the file is not in use by another process`;
150
+
151
+ super(message, 'FILE_OPERATION_ERROR', {
152
+ operation,
153
+ filePath,
154
+ originalError: originalError.message
155
+ });
156
+ this.name = 'FileOperationError';
157
+ this.originalError = originalError;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * JSON validation errors
163
+ */
164
+ class JsonValidationError extends CsvParsingError {
165
+ constructor(csvData, originalError) {
166
+ const message =
167
+ `JSON validation error: The parsed CSV data generated invalid JSON.\n` +
168
+ `This typically indicates malformed field names or values in the CSV.\n` +
169
+ `Original error: ${originalError.message}\n\n` +
170
+ `Solutions:\n` +
171
+ ` 1. Check that field names are valid JavaScript identifiers (or will be converted safely)\n` +
172
+ ` 2. Review the CSV data for special characters that aren't properly escaped\n` +
173
+ ` 3. Enable supportQuotedField(true) for fields containing special characters\n` +
174
+ ` 4. Verify that formatValueByType() isn't converting values incorrectly`;
175
+
176
+ super(message, 'JSON_VALIDATION_ERROR', {
177
+ originalError: originalError.message,
178
+ csvPreview: csvData ? csvData.substring(0, 200) : 'N/A'
179
+ });
180
+ this.name = 'JsonValidationError';
181
+ this.originalError = originalError;
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Browser-specific errors
187
+ */
188
+ class BrowserApiError extends CsvParsingError {
189
+ constructor(message, context = {}) {
190
+ super(message, 'BROWSER_API_ERROR', context);
191
+ this.name = 'BrowserApiError';
192
+ }
193
+
194
+ static fileReaderNotAvailable() {
195
+ return new BrowserApiError(
196
+ `Browser compatibility error: FileReader API is not available.\n` +
197
+ `Your browser does not support the FileReader API required for file parsing.\n\n` +
198
+ `Solutions:\n` +
199
+ ` 1. Use a modern browser that supports FileReader (Chrome 13+, Firefox 10+, Safari 6+)\n` +
200
+ ` 2. Consider using csvStringToJson() or csvStringToJsonAsync() for string-based parsing\n` +
201
+ ` 3. Implement a polyfill or alternative file reading method`
202
+ );
203
+ }
204
+
205
+ static parseFileError(originalError) {
206
+ return new BrowserApiError(
207
+ `Browser file parsing error: Failed to read and parse the file.\n` +
208
+ `Error details: ${originalError.message}\n\n` +
209
+ `Solutions:\n` +
210
+ ` 1. Verify the file is a valid CSV file\n` +
211
+ ` 2. Check the file encoding (UTF-8 is recommended)\n` +
212
+ ` 3. Try a smaller file to isolate the issue\n` +
213
+ ` 4. Check browser console for additional error details`,
214
+ { originalError: originalError.message }
215
+ );
216
+ }
217
+ }
218
+
219
+ module.exports = {
220
+ CsvParsingError,
221
+ InputValidationError,
222
+ ConfigurationError,
223
+ CsvFormatError,
224
+ FileOperationError,
225
+ JsonValidationError,
226
+ BrowserApiError
227
+ };