convert-csv-to-json 4.46.0 → 4.48.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/README.md +9 -0
- package/csvToJson-mockup.html +309 -0
- package/index.d.ts +53 -6
- package/index.js +81 -60
- package/package.json +3 -1
- package/src/browserApi.js +39 -105
- package/src/core/configurable.js +126 -0
- package/src/{util → core}/errors.js +21 -20
- package/src/core/parserConfig.js +37 -0
- package/src/{streamProcessor.js → core/streamProcessor.js} +121 -9
- package/src/csvToJson.js +96 -181
- package/src/csvToJsonAsync.js +15 -102
- package/src/util/fileUtils.js +161 -38
- package/src/util/jsonUtils.js +1 -1
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* Provides
|
|
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('
|
|
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.
|
|
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 {
|
|
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 {
|
|
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.
|
|
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.
|
|
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
|