convert-csv-to-json 4.46.0 → 4.47.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.
@@ -1,15 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * Custom error classes following clean code principles
5
- * Provides clear, actionable error messages with context
6
- * @category Error Classes
7
- */
8
-
9
- /**
10
- * Base class for all CSV parsing errors
11
- * Provides consistent error formatting and context
4
+ * Base class for all CSV parsing errors.
5
+ * Provides consistent error formatting and context.
12
6
  * @category Error Classes
7
+ * @augments Error
13
8
  */
14
9
  class CsvParsingError extends Error {
15
10
  /**
@@ -59,9 +54,10 @@ class CsvParsingError extends Error {
59
54
  }
60
55
 
61
56
  /**
62
- * Input validation errors
63
- * Thrown when function parameters don't meet expected type or value requirements
57
+ * Input validation errors.
58
+ * Thrown when function parameters don't meet expected type or value requirements.
64
59
  * @category Error Classes
60
+ * @augments CsvParsingError
65
61
  */
66
62
  class InputValidationError extends CsvParsingError {
67
63
  /**
@@ -87,9 +83,10 @@ class InputValidationError extends CsvParsingError {
87
83
  }
88
84
 
89
85
  /**
90
- * Configuration-related errors
91
- * Thrown when configuration options conflict or are invalid
86
+ * Configuration-related errors.
87
+ * Thrown when configuration options conflict or are invalid.
92
88
  * @category Error Classes
89
+ * @augments CsvParsingError
93
90
  */
94
91
  class ConfigurationError extends CsvParsingError {
95
92
  /**
@@ -143,9 +140,10 @@ class ConfigurationError extends CsvParsingError {
143
140
  }
144
141
 
145
142
  /**
146
- * CSV parsing errors with detailed context
147
- * Thrown when CSV format is invalid or malformed
143
+ * CSV parsing errors with detailed context.
144
+ * Thrown when CSV format is invalid or malformed.
148
145
  * @category Error Classes
146
+ * @augments CsvParsingError
149
147
  */
150
148
  class CsvFormatError extends CsvParsingError {
151
149
  /**
@@ -203,9 +201,10 @@ class CsvFormatError extends CsvParsingError {
203
201
  }
204
202
 
205
203
  /**
206
- * File operation errors
207
- * Thrown when file read or write operations fail
204
+ * File operation errors.
205
+ * Thrown when file read or write operations fail.
208
206
  * @category Error Classes
207
+ * @augments CsvParsingError
209
208
  */
210
209
  class FileOperationError extends CsvParsingError {
211
210
  /**
@@ -236,9 +235,10 @@ class FileOperationError extends CsvParsingError {
236
235
  }
237
236
 
238
237
  /**
239
- * JSON validation errors
240
- * Thrown when parsed CSV data cannot be converted to valid JSON
238
+ * JSON validation errors.
239
+ * Thrown when parsed CSV data cannot be converted to valid JSON.
241
240
  * @category Error Classes
241
+ * @augments CsvParsingError
242
242
  */
243
243
  class JsonValidationError extends CsvParsingError {
244
244
  /**
@@ -267,9 +267,10 @@ class JsonValidationError extends CsvParsingError {
267
267
  }
268
268
 
269
269
  /**
270
- * Browser-specific errors
271
- * Thrown when browser API operations fail
270
+ * Browser-specific errors.
271
+ * Thrown when browser API operations fail.
272
272
  * @category Error Classes
273
+ * @augments CsvParsingError
273
274
  */
274
275
  class BrowserApiError extends CsvParsingError {
275
276
  /**
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Immutable parser configuration snapshot used for concurrent parsing.
5
+ */
6
+ class ParserConfig {
7
+ /**
8
+ * Create a frozen parser configuration snapshot.
9
+ * @param {object} options - Parser configuration options
10
+ * @param {string} [options.delimiter] - Field delimiter
11
+ * @param {string} [options.encoding] - File encoding
12
+ * @param {boolean} [options.isSupportQuotedField] - Support quoted fields
13
+ * @param {boolean} [options.isTrimHeaderFieldWhiteSpace] - Trim whitespace in header names
14
+ * @param {number} [options.indexHeaderValue] - Header row index
15
+ * @param {string} [options.parseSubArrayDelimiter] - Sub-array delimiter character
16
+ * @param {string} [options.parseSubArraySeparator] - Sub-array item separator
17
+ * @param {boolean} [options.printValueFormatByType] - Format values by type
18
+ * @param {function(object, number): object|null} [options.rowMapper] - Row mapper function
19
+ * @param {Array<number>} [options.indexesToIgnore] - Column indexes to ignore
20
+ */
21
+ constructor(options = {}) {
22
+ this.delimiter = options.delimiter;
23
+ this.encoding = options.encoding;
24
+ this.isSupportQuotedField = options.isSupportQuotedField;
25
+ this.isTrimHeaderFieldWhiteSpace = options.isTrimHeaderFieldWhiteSpace;
26
+ this.indexHeaderValue = options.indexHeaderValue;
27
+ this.parseSubArrayDelimiter = options.parseSubArrayDelimiter;
28
+ this.parseSubArraySeparator = options.parseSubArraySeparator;
29
+ this.printValueFormatByType = options.printValueFormatByType;
30
+ this.rowMapper = options.rowMapper;
31
+ this.indexesToIgnore = options.indexesToIgnore ? Object.freeze([...options.indexesToIgnore]) : Object.freeze([]);
32
+
33
+ Object.freeze(this);
34
+ }
35
+ }
36
+
37
+ module.exports = ParserConfig;
@@ -1,7 +1,7 @@
1
1
  /* globals CsvFormatError */
2
2
  'use strict';
3
3
 
4
- const stringUtils = require('./util/stringUtils');
4
+ const stringUtils = require('../util/stringUtils');
5
5
 
6
6
  const QUOTE_CHAR = '"';
7
7
  const CRLF = '\r\n';
@@ -21,9 +21,9 @@ class StreamProcessor {
21
21
  * @param {object} options - Environment options
22
22
  * @param {boolean} options.isBrowser - Whether running in browser environment
23
23
  * @param {number} options.chunkSize - Number of rows per chunk for callback processing
24
- * @param {function} options.onChunk - Callback for each chunk
25
- * @param {function} options.onComplete - Callback when processing complete
26
- * @param {function} options.onError - Callback for errors
24
+ * @param {function(Array<object>, number, number): void} [options.onChunk] - Callback for each chunk
25
+ * @param {function(Array<object>): void} [options.onComplete] - Callback when processing complete
26
+ * @param {function(Error): void} [options.onError] - Callback for errors
27
27
  */
28
28
  constructor(csvConfig, options = {}) {
29
29
  this.csvConfig = csvConfig;
@@ -31,10 +31,13 @@ class StreamProcessor {
31
31
  this.buffer = '';
32
32
  this.isInsideQuotes = false;
33
33
  this.headers = null;
34
- this.headerRowIndex = csvConfig.getIndexHeader();
34
+ this.headerRowIndex = csvConfig.indexHeaderValue !== null && !isNaN(csvConfig.indexHeaderValue)
35
+ ? csvConfig.indexHeaderValue
36
+ : 0;
35
37
  this.currentRecordIndex = 0;
36
38
  this.parsedRecords = [];
37
39
  this.dataRowIndex = 0;
40
+ this.ignoredIndexes = new Set(csvConfig.indexesToIgnore || []);
38
41
 
39
42
  // Chunked processing options
40
43
  this.chunkSize = options.chunkSize || 1000;
@@ -69,7 +72,7 @@ class StreamProcessor {
69
72
 
70
73
  /**
71
74
  * Process a stream with chunked callbacks (for large files)
72
- * @param {Readable|ReadableStream} stream - The stream to process
75
+ * @param {object} stream - The stream to process (Node.js Readable or browser ReadableStream)
73
76
  * @returns {Promise<void>} Promise that resolves when streaming starts
74
77
  */
75
78
  async processStreamWithCallbacks(stream) {
@@ -176,7 +179,7 @@ class StreamProcessor {
176
179
 
177
180
  /**
178
181
  * Process a stream directly (unified interface for both environments)
179
- * @param {Readable|ReadableStream} stream - The stream to process
182
+ * @param {object} stream - The stream to process (Node.js Readable or browser ReadableStream)
180
183
  * @returns {Promise<Array<object>>} Promise resolving to parsed records
181
184
  */
182
185
  async processStream(stream) {
@@ -324,7 +327,7 @@ class StreamProcessor {
324
327
  _processDataRecord(record) {
325
328
  const dataFields = this._splitRecord(record);
326
329
  if (stringUtils.hasContent(dataFields)) {
327
- const row = this.csvConfig.buildJsonResult(this.headers, dataFields);
330
+ const row = this._buildJsonResult(this.headers, dataFields);
328
331
  const processedRow = this._applyRowMapper(row);
329
332
  if (processedRow !== null) {
330
333
  this.parsedRecords.push(processedRow);
@@ -356,11 +359,120 @@ class StreamProcessor {
356
359
  */
357
360
  _splitRecord(record) {
358
361
  if (this.csvConfig.isSupportQuotedField) {
359
- return this.csvConfig.split(record);
362
+ return this._splitWithConfig(record, this.csvConfig);
360
363
  }
361
364
  return record.split(this.csvConfig.delimiter || ',');
362
365
  }
363
366
 
367
+ /**
368
+ * Split a CSV line into fields using parser configuration rules
369
+ * @param {string} line - The CSV line to split
370
+ * @param {object} config - The CSV parser configuration object
371
+ * @returns {string[]} Array of parsed field values
372
+ * @private
373
+ */
374
+ _splitWithConfig(line, config) {
375
+ if (line.length === 0) {
376
+ return [];
377
+ }
378
+
379
+ const fields = [];
380
+ let currentField = '';
381
+ let insideQuotes = false;
382
+ const delimiter = config.delimiter || ',';
383
+
384
+ for (let i = 0; i < line.length; i++) {
385
+ const char = line[i];
386
+
387
+ if (char === QUOTE_CHAR) {
388
+ if (insideQuotes && i + 1 < line.length && line[i + 1] === QUOTE_CHAR) {
389
+ currentField += QUOTE_CHAR;
390
+ i++;
391
+ } else {
392
+ insideQuotes = !insideQuotes;
393
+ }
394
+ } else if (char === delimiter && !insideQuotes) {
395
+ fields.push(currentField);
396
+ currentField = '';
397
+ } else {
398
+ currentField += char;
399
+ }
400
+ }
401
+
402
+ fields.push(currentField);
403
+
404
+ if (insideQuotes) {
405
+ throw CsvFormatError.mismatchedQuotes('row');
406
+ }
407
+
408
+ return fields;
409
+ }
410
+
411
+ /**
412
+ * Convert a parsed CSV row into a JSON object using header names
413
+ * @param {string[]} headers - Array of header names
414
+ * @param {string[]} currentLine - Array of field values for the current row
415
+ * @returns {object} Parsed row object keyed by header names
416
+ * @private
417
+ */
418
+ _buildJsonResult(headers, currentLine) {
419
+ const jsonObject = {};
420
+ for (let j = 0; j < headers.length; j++) {
421
+ if (this.ignoredIndexes.has(j)) {
422
+ continue;
423
+ }
424
+
425
+ const propertyName = stringUtils.trimPropertyName(this.csvConfig.isTrimHeaderFieldWhiteSpace, headers[j]);
426
+ let value = currentLine[j];
427
+
428
+ if (this._isParseSubArray(value)) {
429
+ value = this._buildJsonSubArray(value);
430
+ }
431
+
432
+ if (this.csvConfig.printValueFormatByType && !Array.isArray(value)) {
433
+ value = stringUtils.getValueFormatByType(currentLine[j]);
434
+ }
435
+
436
+ jsonObject[propertyName] = value;
437
+ }
438
+ return jsonObject;
439
+ }
440
+
441
+ /**
442
+ * Determine whether a field value represents a sub-array expression
443
+ * @param {string} value - The field value to inspect
444
+ * @returns {boolean} True when the value is wrapped in the configured sub-array delimiters
445
+ * @private
446
+ */
447
+ _isParseSubArray(value) {
448
+ if (this.csvConfig.parseSubArrayDelimiter) {
449
+ return value && (value.indexOf(this.csvConfig.parseSubArrayDelimiter) === 0 && value.lastIndexOf(this.csvConfig.parseSubArrayDelimiter) === value.length - 1);
450
+ }
451
+ return false;
452
+ }
453
+
454
+ /**
455
+ * Parse a field value into a JSON sub-array based on configured delimiters and separators
456
+ * @param {string} value - The quoted sub-array string to parse
457
+ * @returns {Array<string|number|boolean>} Parsed sub-array values
458
+ * @private
459
+ */
460
+ _buildJsonSubArray(value) {
461
+ const extractedValues = value.substring(
462
+ value.indexOf(this.csvConfig.parseSubArrayDelimiter) + 1,
463
+ value.lastIndexOf(this.csvConfig.parseSubArrayDelimiter)
464
+ );
465
+ const items = extractedValues.split(this.csvConfig.parseSubArraySeparator);
466
+
467
+ if (this.csvConfig.printValueFormatByType) {
468
+ for (let i = 0; i < items.length; i++) {
469
+ items[i] = stringUtils.getValueFormatByType(items[i]);
470
+ }
471
+ }
472
+
473
+ return items;
474
+ }
475
+
364
476
  /**
365
477
  * Parse complete records from buffer, handling quoted fields across chunks
366
478
  * @param {string} buffer - Current buffer content