convert-csv-to-json 4.14.0 → 4.16.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.
Files changed (38) hide show
  1. package/.github/workflows/ci-cd.yml +1 -1
  2. package/docs/.nojekyll +0 -0
  3. package/docs/README.md +44 -0
  4. package/docs/demo.bundle.js +1708 -0
  5. package/docs/index.html +536 -0
  6. package/eslint.config.js +1 -1
  7. package/package.json +5 -1
  8. package/docs/api/BrowserApi.html +0 -2662
  9. package/docs/api/BrowserApiError.html +0 -669
  10. package/docs/api/ConfigurationError.html +0 -745
  11. package/docs/api/CsvFormatError.html +0 -677
  12. package/docs/api/CsvParsingError.html +0 -511
  13. package/docs/api/CsvToJson.html +0 -3367
  14. package/docs/api/CsvToJsonAsync.html +0 -2880
  15. package/docs/api/FileOperationError.html +0 -382
  16. package/docs/api/FileUtils.html +0 -1150
  17. package/docs/api/InputValidationError.html +0 -407
  18. package/docs/api/JsonUtil.html +0 -452
  19. package/docs/api/JsonValidationError.html +0 -357
  20. package/docs/api/StringUtils.html +0 -833
  21. package/docs/api/global.html +0 -3498
  22. package/docs/api/index.html +0 -414
  23. package/docs/api/index.js.html +0 -447
  24. package/docs/api/scripts/app.min.js +0 -1
  25. package/docs/api/scripts/linenumber.js +0 -26
  26. package/docs/api/scripts/search.js +0 -39
  27. package/docs/api/src_browserApi.js.html +0 -362
  28. package/docs/api/src_csvToJson.js.html +0 -696
  29. package/docs/api/src_csvToJsonAsync.js.html +0 -335
  30. package/docs/api/src_util_errors.js.html +0 -472
  31. package/docs/api/src_util_fileUtils.js.html +0 -238
  32. package/docs/api/src_util_jsonUtils.js.html +0 -166
  33. package/docs/api/src_util_stringUtils.js.html +0 -306
  34. package/docs/api/styles/app.min.css +0 -1
  35. package/docs/api/styles/iframe.css +0 -13
  36. package/docs/api/styles/prettify-jsdoc.css +0 -111
  37. package/docs/api/styles/prettify-tomorrow.css +0 -132
  38. package/docs/api/styles/reset.css +0 -44
@@ -0,0 +1,1708 @@
1
+ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.demo = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
2
+ // Demo application for CSV to JSON converter
3
+ // This will be bundled with the library using browserify
4
+
5
+ const csvToJson = require('../src/browserApi');
6
+
7
+ (function() {
8
+ 'use strict';
9
+
10
+ // Initialize when DOM is ready
11
+ document.addEventListener('DOMContentLoaded', function() {
12
+ setupEventListeners();
13
+ });
14
+
15
+ function setupEventListeners() {
16
+ // Tab switching
17
+ document.querySelectorAll('.tab').forEach(tab => {
18
+ tab.addEventListener('click', function() {
19
+ showTab(this.getAttribute('data-tab'));
20
+ });
21
+ });
22
+
23
+ // Output tab switching
24
+ document.querySelectorAll('.output-tab').forEach(tab => {
25
+ tab.addEventListener('click', function() {
26
+ showOutputTab(this.getAttribute('data-output'));
27
+ });
28
+ });
29
+
30
+ // Convert button
31
+ document.getElementById('convert-btn').addEventListener('click', convert);
32
+
33
+ // Clear button
34
+ document.getElementById('clear-btn').addEventListener('click', clearAll);
35
+
36
+ // Options change listeners
37
+ document.getElementById('format-values').addEventListener('change', updateOptions);
38
+ document.getElementById('quoted-fields').addEventListener('change', updateOptions);
39
+ document.getElementById('delimiter').addEventListener('input', updateOptions);
40
+ document.getElementById('header-index').addEventListener('input', updateOptions);
41
+
42
+ // File input change
43
+ document.getElementById('csv-file').addEventListener('change', handleFileSelect);
44
+
45
+ // Sample data buttons
46
+ document.querySelectorAll('.sample-btn').forEach(btn => {
47
+ btn.addEventListener('click', function() {
48
+ loadSample(this.getAttribute('data-sample'));
49
+ });
50
+ });
51
+ }
52
+
53
+ function showTab(tabName) {
54
+ // Hide all tabs
55
+ document.querySelectorAll('.tab-content').forEach(content => {
56
+ content.classList.remove('active');
57
+ });
58
+ document.querySelectorAll('.tab').forEach(tab => {
59
+ tab.classList.remove('active');
60
+ });
61
+
62
+ // Show selected tab
63
+ document.getElementById(tabName + '-tab').classList.add('active');
64
+ document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
65
+ }
66
+
67
+ function showOutputTab(tabName) {
68
+ // Hide all output tabs
69
+ document.querySelectorAll('.output-content').forEach(content => {
70
+ content.classList.remove('active');
71
+ });
72
+ document.querySelectorAll('.output-tab').forEach(tab => {
73
+ tab.classList.remove('active');
74
+ });
75
+
76
+ // Show selected output tab
77
+ document.getElementById(tabName + '-content').classList.add('active');
78
+ document.querySelector(`[data-output="${tabName}"]`).classList.add('active');
79
+ }
80
+
81
+ function updateOptions() {
82
+ const formatValues = document.getElementById('format-values').checked;
83
+ const quotedFields = document.getElementById('quoted-fields').checked;
84
+ const delimiter = document.getElementById('delimiter').value;
85
+ const headerIndex = parseInt(document.getElementById('header-index').value) || 0;
86
+
87
+ csvToJson.formatValueByType(formatValues);
88
+ csvToJson.supportQuotedField(quotedFields);
89
+ csvToJson.fieldDelimiter(delimiter);
90
+ csvToJson.indexHeader(headerIndex);
91
+ }
92
+
93
+ async function convert() {
94
+ const output = document.getElementById('output');
95
+ const convertBtn = document.getElementById('convert-btn');
96
+
97
+ // Clear previous output
98
+ clearOutput();
99
+
100
+ // Disable button during conversion
101
+ convertBtn.disabled = true;
102
+ convertBtn.textContent = 'Converting...';
103
+
104
+ try {
105
+ let result;
106
+
107
+ // Check which tab is active
108
+ const activeTab = document.querySelector('.tab-content.active').id;
109
+ if (activeTab === 'text-tab') {
110
+ // Text input
111
+ const csvText = document.getElementById('csv-input').value;
112
+ if (!csvText.trim()) {
113
+ throw new Error('Please enter CSV text');
114
+ }
115
+ result = csvToJson.csvStringToJson(csvText);
116
+ } else {
117
+ // File input
118
+ const fileInput = document.getElementById('csv-file');
119
+ if (!fileInput.files[0]) {
120
+ throw new Error('Please select a CSV file');
121
+ }
122
+ result = await csvToJson.parseFile(fileInput.files[0]);
123
+ }
124
+
125
+ // Display result
126
+ displayResult(result);
127
+
128
+ } catch (error) {
129
+ displayError(error);
130
+ } finally {
131
+ // Re-enable button
132
+ convertBtn.disabled = false;
133
+ convertBtn.textContent = 'Convert to JSON';
134
+ }
135
+ }
136
+
137
+ function displayResult(result) {
138
+ const output = document.getElementById('output');
139
+ const jsonOutput = document.getElementById('json-output');
140
+ const tableOutput = document.getElementById('table-output');
141
+ const statsOutput = document.getElementById('stats-output');
142
+
143
+ // Show results container
144
+ output.classList.remove('hidden');
145
+
146
+ // JSON output
147
+ jsonOutput.textContent = JSON.stringify(result, null, 2);
148
+
149
+ // Table output
150
+ displayTable(result);
151
+
152
+ // Stats
153
+ displayStats(result);
154
+
155
+ // Switch to table view by default
156
+ showOutputTab('table');
157
+ }
158
+
159
+ function displayTable(data) {
160
+ const tableOutput = document.getElementById('table-output');
161
+ if (!data || data.length === 0) {
162
+ tableOutput.innerHTML = '<p>No data to display</p>';
163
+ return;
164
+ }
165
+
166
+ let html = '<table><thead><tr>';
167
+ // Headers
168
+ Object.keys(data[0]).forEach(key => {
169
+ html += `<th>${escapeHtml(key)}</th>`;
170
+ });
171
+ html += '</tr></thead><tbody>';
172
+
173
+ // Rows
174
+ data.forEach(row => {
175
+ html += '<tr>';
176
+ Object.values(row).forEach(value => {
177
+ html += `<td>${escapeHtml(String(value))}</td>`;
178
+ });
179
+ html += '</tr>';
180
+ });
181
+ html += '</tbody></table>';
182
+
183
+ tableOutput.innerHTML = html;
184
+ }
185
+
186
+ function displayStats(data) {
187
+ const statsOutput = document.getElementById('stats-output');
188
+ if (!data || data.length === 0) {
189
+ statsOutput.innerHTML = '<p>No data</p>';
190
+ return;
191
+ }
192
+
193
+ const numRows = data.length;
194
+ const numCols = Object.keys(data[0]).length;
195
+ const size = JSON.stringify(data).length;
196
+
197
+ statsOutput.innerHTML = `
198
+ <div><strong>Rows:</strong> ${numRows}</div>
199
+ <div><strong>Columns:</strong> ${numCols}</div>
200
+ <div><strong>JSON Size:</strong> ${size} characters</div>
201
+ `;
202
+ }
203
+
204
+ function displayError(error) {
205
+ const output = document.getElementById('output');
206
+ const errorOutput = document.getElementById('error-output');
207
+
208
+ output.classList.remove('hidden');
209
+ errorOutput.textContent = error.message;
210
+ errorOutput.classList.remove('hidden');
211
+ }
212
+
213
+ function clearOutput() {
214
+ const output = document.getElementById('output');
215
+ const errorOutput = document.getElementById('error-output');
216
+
217
+ output.classList.add('hidden');
218
+ errorOutput.classList.add('hidden');
219
+ errorOutput.textContent = '';
220
+ }
221
+
222
+ function clearAll() {
223
+ document.getElementById('csv-input').value = '';
224
+ document.getElementById('csv-file').value = '';
225
+ clearOutput();
226
+ }
227
+
228
+ function handleFileSelect(event) {
229
+ const file = event.target.files[0];
230
+ if (file) {
231
+ // Show file info
232
+ const fileInfo = document.getElementById('file-info');
233
+ fileInfo.textContent = `Selected: ${file.name} (${formatFileSize(file.size)})`;
234
+ fileInfo.classList.remove('hidden');
235
+ }
236
+ }
237
+
238
+ function loadSample(sampleName) {
239
+ const samples = {
240
+ basic: `name,age,city
241
+ John,25,New York
242
+ Jane,30,London
243
+ Bob,35,Paris`,
244
+
245
+ quoted: `"name","age","description"
246
+ "John Doe","25","Software Engineer"
247
+ "Jane Smith","30","Product Manager"`,
248
+
249
+ numbers: `product,price,quantity
250
+ Widget A,19.99,100
251
+ Widget B,29.99,50
252
+ Widget C,9.99,200`,
253
+
254
+ dates: `event,date,attendees
255
+ Conference,2024-01-15,150
256
+ Workshop,2024-02-20,75
257
+ Seminar,2024-03-10,200`
258
+ };
259
+
260
+ document.getElementById('csv-input').value = samples[sampleName] || '';
261
+ showTab('text');
262
+ }
263
+
264
+ function formatFileSize(bytes) {
265
+ if (bytes === 0) return '0 Bytes';
266
+ const k = 1024;
267
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
268
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
269
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
270
+ }
271
+
272
+ function escapeHtml(text) {
273
+ const div = document.createElement('div');
274
+ div.textContent = text;
275
+ return div.innerHTML;
276
+ }
277
+
278
+ function downloadJSON() {
279
+ const jsonOutput = document.getElementById('json-output');
280
+ const data = jsonOutput.textContent;
281
+ const blob = new Blob([data], { type: 'application/json' });
282
+ const url = URL.createObjectURL(blob);
283
+
284
+ const a = document.createElement('a');
285
+ a.href = url;
286
+ a.download = 'converted-data.json';
287
+ document.body.appendChild(a);
288
+ a.click();
289
+ document.body.removeChild(a);
290
+ URL.revokeObjectURL(url);
291
+ }
292
+
293
+ // Make functions globally available for inline event handlers
294
+ window.showTab = showTab;
295
+ window.convert = convert;
296
+ window.clearAll = clearAll;
297
+ window.downloadJSON = downloadJSON;
298
+ })();
299
+ },{"../src/browserApi":3}],2:[function(require,module,exports){
300
+
301
+ },{}],3:[function(require,module,exports){
302
+ /* globals CsvFormatError */
303
+
304
+ "use strict";
305
+
306
+ const csvToJson = require('./csvToJson');
307
+ const { InputValidationError, BrowserApiError } = require('./util/errors');
308
+
309
+ /**
310
+ * Browser-friendly CSV to JSON API
311
+ * Provides methods for parsing CSV strings and File/Blob objects in browser environments
312
+ * Proxies configuration to sync csvToJson instance
313
+ * @category 4-Browser
314
+ */
315
+ class BrowserApi {
316
+ /**
317
+ * Constructor initializes proxy to sync csvToJson instance
318
+ */
319
+ constructor() {
320
+ // reuse the existing csvToJson instance for parsing and configuration
321
+ this.csvToJson = csvToJson;
322
+ }
323
+
324
+ /**
325
+ * Enable or disable automatic type formatting for values
326
+ * @param {boolean} active - Whether to format values by type (default: true)
327
+ * @returns {this} For method chaining
328
+ */
329
+ formatValueByType(active = true) {
330
+ this.csvToJson.formatValueByType(active);
331
+ return this;
332
+ }
333
+
334
+ /**
335
+ * Enable or disable support for RFC 4180 quoted fields
336
+ * @param {boolean} active - Whether to support quoted fields (default: false)
337
+ * @returns {this} For method chaining
338
+ */
339
+ supportQuotedField(active = false) {
340
+ this.csvToJson.supportQuotedField(active);
341
+ return this;
342
+ }
343
+
344
+ /**
345
+ * Set the field delimiter character
346
+ * @param {string} delimiter - Character(s) to use as field separator
347
+ * @returns {this} For method chaining
348
+ */
349
+ fieldDelimiter(delimiter) {
350
+ this.csvToJson.fieldDelimiter(delimiter);
351
+ return this;
352
+ }
353
+
354
+ /**
355
+ * Configure whitespace handling in header field names
356
+ * @param {boolean} active - If true, removes all whitespace; if false, only trims edges (default: false)
357
+ * @returns {this} For method chaining
358
+ */
359
+ trimHeaderFieldWhiteSpace(active = false) {
360
+ this.csvToJson.trimHeaderFieldWhiteSpace(active);
361
+ return this;
362
+ }
363
+
364
+ /**
365
+ * Set the row index where CSV headers are located
366
+ * @param {number} index - Zero-based row index containing headers
367
+ * @returns {this} For method chaining
368
+ */
369
+ indexHeader(index) {
370
+ this.csvToJson.indexHeader(index);
371
+ return this;
372
+ }
373
+
374
+ /**
375
+ * Configure sub-array parsing for special field values
376
+ * @param {string} delimiter - Bracket character (default: '*')
377
+ * @param {string} separator - Item separator within brackets (default: ',')
378
+ * @returns {this} For method chaining
379
+ */
380
+ parseSubArray(delimiter = '*', separator = ',') {
381
+ this.csvToJson.parseSubArray(delimiter, separator);
382
+ return this;
383
+ }
384
+
385
+ /**
386
+ * Set a mapper function to transform each row after conversion
387
+ * @param {function(object, number): (object|null)} mapperFn - Function receiving (row, index) that returns transformed row or null to filter
388
+ * @returns {this} For method chaining
389
+ */
390
+ mapRows(mapperFn) {
391
+ this.csvToJson.mapRows(mapperFn);
392
+ return this;
393
+ }
394
+
395
+ /**
396
+ * Parse a CSV string and return as JSON array of objects
397
+ * @param {string} csvString - CSV content as string
398
+ * @returns {Array<object>} Array of objects representing CSV rows
399
+ * @throws {InputValidationError} If csvString is invalid
400
+ * @throws {CsvFormatError} If CSV is malformed
401
+ * @example
402
+ * const csvToJson = require('convert-csv-to-json');
403
+ * const rows = csvToJson.browser.csvStringToJson('name,age\nAlice,30');
404
+ * console.log(rows); // [{ name: 'Alice', age: '30' }]
405
+ */
406
+ csvStringToJson(csvString) {
407
+ if (csvString === undefined || csvString === null) {
408
+ throw new InputValidationError(
409
+ 'csvString',
410
+ 'string',
411
+ `${typeof csvString}`,
412
+ 'Provide valid CSV content as a string to parse.'
413
+ );
414
+ }
415
+ return this.csvToJson.csvToJson(csvString);
416
+ }
417
+
418
+ /**
419
+ * Parse a CSV string and return as stringified JSON
420
+ * @param {string} csvString - CSV content as string
421
+ * @returns {string} JSON stringified array of objects
422
+ * @throws {InputValidationError} If csvString is invalid
423
+ * @throws {CsvFormatError} If CSV is malformed
424
+ * @example
425
+ * const csvToJson = require('convert-csv-to-json');
426
+ * const jsonString = csvToJson.browser.csvStringToJsonStringified('name,age\nAlice,30');
427
+ * console.log(jsonString);
428
+ */
429
+ csvStringToJsonStringified(csvString) {
430
+ if (csvString === undefined || csvString === null) {
431
+ throw new InputValidationError(
432
+ 'csvString',
433
+ 'string',
434
+ `${typeof csvString}`,
435
+ 'Provide valid CSV content as a string to parse.'
436
+ );
437
+ }
438
+ return this.csvToJson.csvStringToJsonStringified(csvString);
439
+ }
440
+
441
+ /**
442
+ * Parse a CSV string asynchronously (returns resolved Promise)
443
+ * @param {string} csvString - CSV content as string
444
+ * @returns {Promise<Array<object>>} Promise resolving to array of objects
445
+ * @throws {InputValidationError} If csvString is invalid
446
+ * @throws {CsvFormatError} If CSV is malformed
447
+ * @example
448
+ * const csvToJson = require('convert-csv-to-json');
449
+ * const rows = await csvToJson.browser.csvStringToJsonAsync('name,age\nAlice,30');
450
+ * console.log(rows);
451
+ */
452
+ csvStringToJsonAsync(csvString) {
453
+ return Promise.resolve(this.csvStringToJson(csvString));
454
+ }
455
+
456
+ /**
457
+ * Parse a CSV string asynchronously and return as stringified JSON
458
+ * @param {string} csvString - CSV content as string
459
+ * @returns {Promise<string>} Promise resolving to JSON stringified array
460
+ * @throws {InputValidationError} If csvString is invalid
461
+ * @throws {CsvFormatError} If CSV is malformed
462
+ * @example
463
+ * const csvToJson = require('convert-csv-to-json');
464
+ * const json = await csvToJson.browser.csvStringToJsonStringifiedAsync('name,age\nAlice,30');
465
+ * console.log(json);
466
+ */
467
+ csvStringToJsonStringifiedAsync(csvString) {
468
+ return Promise.resolve(this.csvStringToJsonStringified(csvString));
469
+ }
470
+
471
+ /**
472
+ * Parse a browser File or Blob object to JSON array.
473
+ * @param {File|Blob} file - File or Blob to read as text
474
+ * @param {object} [options] - options: { encoding?: string }
475
+ * @returns {Promise<object[]>} Promise resolving to parsed JSON rows
476
+ * @example
477
+ * const csvToJson = require('convert-csv-to-json');
478
+ * const fileInput = document.querySelector('#csvfile').files[0];
479
+ * const rows = await csvToJson.browser.parseFile(fileInput);
480
+ * console.log(rows);
481
+ */
482
+ parseFile(file, options = {}) {
483
+ if (!file) {
484
+ return Promise.reject(new InputValidationError(
485
+ 'file',
486
+ 'File or Blob object',
487
+ `${typeof file}`,
488
+ 'Provide a valid File or Blob object to parse.'
489
+ ));
490
+ }
491
+
492
+ return new Promise((resolve, reject) => {
493
+ if (typeof FileReader === 'undefined') {
494
+ reject(BrowserApiError.fileReaderNotAvailable());
495
+ return;
496
+ }
497
+
498
+ const reader = new FileReader();
499
+ reader.onerror = () => reject(BrowserApiError.parseFileError(
500
+ reader.error || new Error('Unknown file reading error')
501
+ ));
502
+ reader.onload = () => {
503
+ try {
504
+ const text = reader.result;
505
+ const result = this.csvToJson.csvToJson(String(text));
506
+ resolve(result);
507
+ } catch (err) {
508
+ reject(BrowserApiError.parseFileError(err));
509
+ }
510
+ };
511
+
512
+ // If encoding is provided, pass it to readAsText
513
+ if (options.encoding) {
514
+ reader.readAsText(file, options.encoding);
515
+ } else {
516
+ reader.readAsText(file);
517
+ }
518
+ });
519
+ }
520
+ }
521
+
522
+ module.exports = new BrowserApi();
523
+
524
+ },{"./csvToJson":4,"./util/errors":5}],4:[function(require,module,exports){
525
+ /* globals FileOperationError */
526
+ "use strict";
527
+
528
+ const fileUtils = require('./util/fileUtils');
529
+ const stringUtils = require('./util/stringUtils');
530
+ const jsonUtils = require('./util/jsonUtils');
531
+ const {
532
+ ConfigurationError,
533
+ CsvFormatError,
534
+ JsonValidationError
535
+ } = require('./util/errors');
536
+
537
+ const DEFAULT_FIELD_DELIMITER = ",";
538
+ const QUOTE_CHAR = '"';
539
+ const CRLF = '\r\n';
540
+ const LF = '\n';
541
+ const CR = '\r';
542
+
543
+ /**
544
+ * Main CSV to JSON converter class
545
+ * Provides chainable API for configuring and converting CSV data
546
+ * @category 2-Sync
547
+ */
548
+ class CsvToJson {
549
+
550
+ /**
551
+ * Enable or disable automatic type formatting for values
552
+ * When enabled, numeric strings are converted to numbers, 'true'/'false' to booleans
553
+ * @param {boolean} active - Whether to format values by type
554
+ * @returns {this} For method chaining
555
+ */
556
+ formatValueByType(active) {
557
+ this.printValueFormatByType = active;
558
+ return this;
559
+ }
560
+
561
+ /**
562
+ * Enable or disable support for RFC 4180 quoted fields
563
+ * When enabled, fields wrapped in double quotes can contain delimiters and newlines
564
+ * @param {boolean} active - Whether to support quoted fields
565
+ * @returns {this} For method chaining
566
+ */
567
+ supportQuotedField(active) {
568
+ this.isSupportQuotedField = active;
569
+ return this;
570
+ }
571
+
572
+ /**
573
+ * Set the field delimiter character
574
+ * @param {string} delimiter - Character(s) to use as field separator (default: ',')
575
+ * @returns {this} For method chaining
576
+ */
577
+ fieldDelimiter(delimiter) {
578
+ this.delimiter = delimiter;
579
+ return this;
580
+ }
581
+
582
+ /**
583
+ * Configure whitespace handling in header field names
584
+ * @param {boolean} active - If true, removes all whitespace from header names; if false, only trims edges
585
+ * @returns {this} For method chaining
586
+ */
587
+ trimHeaderFieldWhiteSpace(active) {
588
+ this.isTrimHeaderFieldWhiteSpace = active;
589
+ return this;
590
+ }
591
+
592
+ /**
593
+ * Set the row index where CSV headers are located
594
+ * @param {number} indexHeaderValue - Zero-based row index containing headers
595
+ * @returns {this} For method chaining
596
+ * @throws {ConfigurationError} If not a valid number
597
+ */
598
+ indexHeader(indexHeaderValue) {
599
+ if (isNaN(indexHeaderValue)) {
600
+ throw ConfigurationError.invalidHeaderIndex(indexHeaderValue);
601
+ }
602
+ this.indexHeaderValue = indexHeaderValue;
603
+ return this;
604
+ }
605
+
606
+
607
+ /**
608
+ * Configure sub-array parsing for special field values
609
+ * Fields bracketed by delimiter and containing separator are parsed into arrays
610
+ * @param {string} delimiter - Bracket character (default: '*')
611
+ * @param {string} separator - Item separator within brackets (default: ',')
612
+ * @returns {this} For method chaining
613
+ * @example
614
+ * // Input: "*val1,val2,val3*"
615
+ * // Output: ["val1", "val2", "val3"]
616
+ * .parseSubArray('*', ',')
617
+ */
618
+ parseSubArray(delimiter = '*',separator = ',') {
619
+ this.parseSubArrayDelimiter = delimiter;
620
+ this.parseSubArraySeparator = separator;
621
+ return this;
622
+ }
623
+
624
+ /**
625
+ * Set file encoding for reading CSV files
626
+ * @param {string} encoding - Node.js supported encoding (e.g., 'utf8', 'latin1', 'ascii')
627
+ * @returns {this} For method chaining
628
+ */
629
+ encoding(encoding){
630
+ this.encoding = encoding;
631
+ return this;
632
+ }
633
+
634
+ /**
635
+ * Sets a mapper function to transform each row after conversion
636
+ * @param {function(object, number): (object|null)} mapperFn - Function that receives (row, index) and returns transformed row or null to filter out
637
+ * @returns {this} For method chaining
638
+ */
639
+ mapRows(mapperFn) {
640
+ if (typeof mapperFn !== 'function') {
641
+ throw new TypeError('mapperFn must be a function');
642
+ }
643
+ this.rowMapper = mapperFn;
644
+ return this;
645
+ }
646
+
647
+ /**
648
+ * Read a CSV file and write the parsed JSON to an output file
649
+ * @param {string} fileInputName - Path to input CSV file
650
+ * @param {string} fileOutputName - Path to output JSON file
651
+ * @throws {FileOperationError} If file read or write fails
652
+ * @throws {CsvFormatError} If CSV is malformed
653
+ */
654
+ generateJsonFileFromCsv(fileInputName, fileOutputName) {
655
+ let jsonStringified = this.getJsonFromCsvStringified(fileInputName);
656
+ fileUtils.writeFile(jsonStringified, fileOutputName);
657
+ }
658
+
659
+ /**
660
+ * Read a CSV file and return parsed data as stringified JSON
661
+ * @param {string} fileInputName - Path to input CSV file
662
+ * @returns {string} JSON stringified array of objects
663
+ * @throws {FileOperationError} If file read fails
664
+ * @throws {CsvFormatError} If CSV is malformed
665
+ * @throws {JsonValidationError} If JSON generation fails
666
+ * @example
667
+ * const csvToJson = require('convert-csv-to-json');
668
+ * const jsonString = csvToJson.getJsonFromCsvStringified('resource/input.csv');
669
+ * console.log(jsonString);
670
+ */
671
+ getJsonFromCsvStringified(fileInputName) {
672
+ let json = this.getJsonFromCsv(fileInputName);
673
+ let jsonStringified = JSON.stringify(json, undefined, 1);
674
+ jsonUtils.validateJson(jsonStringified);
675
+ return jsonStringified;
676
+ }
677
+
678
+ /**
679
+ * Read a CSV file and return parsed data as JSON array of objects
680
+ * @param {string} fileInputName - Path to input CSV file
681
+ * @returns {Array<object>} Array of objects representing CSV rows
682
+ * @throws {FileOperationError} If file read fails
683
+ * @throws {CsvFormatError} If CSV is malformed
684
+ * @example
685
+ * const csvToJson = require('convert-csv-to-json');
686
+ * const rows = csvToJson.getJsonFromCsv('resource/input.csv');
687
+ * console.log(rows);
688
+ */
689
+ getJsonFromCsv(fileInputName) {
690
+ let parsedCsv = fileUtils.readFile(fileInputName, this.encoding);
691
+ return this.csvToJson(parsedCsv);
692
+ }
693
+
694
+ /**
695
+ * Parse CSV string content and return as JSON array of objects
696
+ * @param {string} csvString - CSV content as string
697
+ * @returns {Array<object>} Array of objects representing CSV rows
698
+ * @throws {CsvFormatError} If CSV is malformed
699
+ * @example
700
+ * const csvToJson = require('convert-csv-to-json');
701
+ * const rows = csvToJson.csvStringToJson('name,age\nAlice,30');
702
+ * console.log(rows); // [{ name: 'Alice', age: '30' }]
703
+ */
704
+ csvStringToJson(csvString) {
705
+ return this.csvToJson(csvString);
706
+ }
707
+
708
+ /**
709
+ * Parse CSV string content and return as stringified JSON
710
+ * @param {string} csvString - CSV content as string
711
+ * @returns {string} JSON stringified array of objects
712
+ * @throws {CsvFormatError} If CSV is malformed
713
+ * @throws {JsonValidationError} If JSON generation fails
714
+ * @example
715
+ * const csvToJson = require('convert-csv-to-json');
716
+ * const jsonString = csvToJson.csvStringToJsonStringified('name,age\nAlice,30');
717
+ * console.log(jsonString);
718
+ */
719
+ csvStringToJsonStringified(csvString) {
720
+ let json = this.csvStringToJson(csvString);
721
+ let jsonStringified = JSON.stringify(json, undefined, 1);
722
+ jsonUtils.validateJson(jsonStringified);
723
+ return jsonStringified;
724
+ }
725
+
726
+ /**
727
+ * Core CSV parsing logic - converts CSV string to JSON array
728
+ * Handles quoted fields per RFC 4180 when configured
729
+ * Applies row mapping and filtering when configured
730
+ * @param {string} parsedCsv - Raw CSV content as string
731
+ * @returns {Array<object>} Array of objects with CSV data
732
+ * @private
733
+ */
734
+ csvToJson(parsedCsv) {
735
+ this.validateInputConfig();
736
+
737
+ // Parse CSV into individual records, respecting quoted fields that may contain newlines
738
+ let records = this.parseRecords(parsedCsv);
739
+
740
+ let fieldDelimiter = this.getFieldDelimiter();
741
+ let index = this.getIndexHeader();
742
+ let headers;
743
+
744
+ // Find the header row
745
+ while (index < records.length) {
746
+ if (this.isSupportQuotedField) {
747
+ headers = this.split(records[index]);
748
+ } else {
749
+ headers = records[index].split(fieldDelimiter);
750
+ }
751
+
752
+ if (stringUtils.hasContent(headers)) {
753
+ break;
754
+ }
755
+ index++;
756
+ }
757
+
758
+ if (!headers) {
759
+ throw CsvFormatError.missingHeader();
760
+ }
761
+
762
+ let jsonResult = [];
763
+ for (let i = (index + 1); i < records.length; i++) {
764
+ let currentLine;
765
+ if (this.isSupportQuotedField) {
766
+ currentLine = this.split(records[i]);
767
+ } else {
768
+ currentLine = records[i].split(fieldDelimiter);
769
+ }
770
+
771
+ if (stringUtils.hasContent(currentLine)) {
772
+ let row = this.buildJsonResult(headers, currentLine);
773
+
774
+ // Apply row mapper if defined
775
+ if (this.rowMapper) {
776
+ row = this.rowMapper(row, i - (index + 1)); // Pass row and 0-based row index
777
+ // If mapper returns null/undefined, skip this row (allows filtering)
778
+ if (row != null) {
779
+ jsonResult.push(row);
780
+ }
781
+ } else {
782
+ jsonResult.push(row);
783
+ }
784
+ }
785
+ }
786
+ return jsonResult;
787
+ }
788
+
789
+ /**
790
+ * Parse CSV content into individual records, respecting quoted fields that may span multiple lines.
791
+ * RFC 4180 compliant parsing - handles quoted fields that may contain newlines.
792
+ * @param {string} csvContent - The raw CSV content
793
+ * @returns {string[]} Array of record strings
794
+ */
795
+ parseRecords(csvContent) {
796
+ let records = [];
797
+ let currentRecord = '';
798
+ let insideQuotes = false;
799
+ let i = 0;
800
+
801
+ while (i < csvContent.length) {
802
+ let char = csvContent[i];
803
+
804
+ // Handle quote characters
805
+ if (char === QUOTE_CHAR) {
806
+ if (insideQuotes && i + 1 < csvContent.length && csvContent[i + 1] === QUOTE_CHAR) {
807
+ // Escaped quote: two consecutive quotes = single quote representation
808
+ currentRecord += QUOTE_CHAR + QUOTE_CHAR;
809
+ i += 2;
810
+ } else {
811
+ // Toggle quote state
812
+ insideQuotes = !insideQuotes;
813
+ currentRecord += char;
814
+ i++;
815
+ }
816
+ continue;
817
+ }
818
+
819
+ // Handle line endings (only outside quoted fields)
820
+ if (!insideQuotes) {
821
+ let lineEndingLength = this.getLineEndingLength(csvContent, i);
822
+ if (lineEndingLength > 0) {
823
+ records.push(currentRecord);
824
+ currentRecord = '';
825
+ i += lineEndingLength;
826
+ continue;
827
+ }
828
+ }
829
+
830
+ // Regular character
831
+ currentRecord += char;
832
+ i++;
833
+ }
834
+
835
+ // Add the last record if not empty
836
+ if (currentRecord.length > 0) {
837
+ records.push(currentRecord);
838
+ }
839
+
840
+ // Validate matching quotes
841
+ if (insideQuotes) {
842
+ throw CsvFormatError.mismatchedQuotes('CSV');
843
+ }
844
+
845
+ return records;
846
+ }
847
+
848
+ /**
849
+ * Get the length of line ending at current position (CRLF=2, LF=1, CR=1, or 0)
850
+ * @param {string} content - CSV content
851
+ * @param {number} index - Current index to check
852
+ * @returns {number} Length of line ending (0 if none)
853
+ */
854
+ getLineEndingLength(content, index) {
855
+ if (content.slice(index, index + 2) === CRLF) {
856
+ return 2;
857
+ }
858
+ if (content[index] === LF) {
859
+ return 1;
860
+ }
861
+ if (content[index] === CR && content[index + 1] !== LF) {
862
+ return 1;
863
+ }
864
+ return 0;
865
+ }
866
+
867
+ /**
868
+ * Get the configured field delimiter, or default if not set
869
+ * @returns {string} Field delimiter character
870
+ * @private
871
+ */
872
+ getFieldDelimiter() {
873
+ if (this.delimiter) {
874
+ return this.delimiter;
875
+ }
876
+ return DEFAULT_FIELD_DELIMITER;
877
+ }
878
+
879
+ /**
880
+ * Get the configured header row index, or default (0) if not set
881
+ * @returns {number} Header row index
882
+ * @private
883
+ */
884
+ getIndexHeader(){
885
+ if(this.indexHeaderValue !== null && !isNaN(this.indexHeaderValue)){
886
+ return this.indexHeaderValue;
887
+ }
888
+ return 0;
889
+ }
890
+
891
+ /**
892
+ * Build a JSON object from headers and field values
893
+ * Applies type formatting and sub-array parsing as configured
894
+ * @param {string[]} headers - Array of header field names
895
+ * @param {string[]} currentLine - Array of field values
896
+ * @returns {object} JSON object with header names as keys
897
+ * @private
898
+ */
899
+ buildJsonResult(headers, currentLine) {
900
+ let jsonObject = {};
901
+ for (let j = 0; j < headers.length; j++) {
902
+ let propertyName = stringUtils.trimPropertyName(this.isTrimHeaderFieldWhiteSpace, headers[j]);
903
+ let value = currentLine[j];
904
+
905
+ if(this.isParseSubArray(value)){
906
+ value = this.buildJsonSubArray(value);
907
+ }
908
+
909
+ if (this.printValueFormatByType && !Array.isArray(value)) {
910
+ value = stringUtils.getValueFormatByType(currentLine[j]);
911
+ }
912
+
913
+ jsonObject[propertyName] = value;
914
+ }
915
+ return jsonObject;
916
+ }
917
+
918
+ /**
919
+ * Parse a field value into a sub-array using configured delimiter and separator
920
+ * @param {string} value - Field value to parse
921
+ * @returns {Array<string|number|boolean>} Array of parsed values
922
+ * @private
923
+ */
924
+ buildJsonSubArray(value) {
925
+ let extractedValues = value.substring(
926
+ value.indexOf(this.parseSubArrayDelimiter) + 1,
927
+ value.lastIndexOf(this.parseSubArrayDelimiter)
928
+ );
929
+ extractedValues.trim();
930
+ value = extractedValues.split(this.parseSubArraySeparator);
931
+ if(this.printValueFormatByType){
932
+ for(let i=0; i < value.length; i++){
933
+ value[i] = stringUtils.getValueFormatByType(value[i]);
934
+ }
935
+ }
936
+ return value;
937
+ }
938
+
939
+ /**
940
+ * Check if a field value should be parsed as a sub-array
941
+ * @param {string} value - Field value to check
942
+ * @returns {boolean} True if value is bracketed with sub-array delimiter
943
+ * @private
944
+ */
945
+ isParseSubArray(value){
946
+ if(this.parseSubArrayDelimiter){
947
+ if (value && (value.indexOf(this.parseSubArrayDelimiter) === 0 && value.lastIndexOf(this.parseSubArrayDelimiter) === (value.length - 1))) {
948
+ return true;
949
+ }
950
+ }
951
+ return false;
952
+ }
953
+
954
+ /**
955
+ * Validate configuration for conflicts and incompatibilities
956
+ * @throws {ConfigurationError} If incompatible options are set
957
+ * @private
958
+ */
959
+ validateInputConfig(){
960
+ if(this.isSupportQuotedField) {
961
+ if(this.getFieldDelimiter() === '"'){
962
+ throw ConfigurationError.quotedFieldConflict('fieldDelimiter', '"');
963
+ }
964
+ if(this.parseSubArraySeparator === '"'){
965
+ throw ConfigurationError.quotedFieldConflict('parseSubArraySeparator', '"');
966
+ }
967
+ if(this.parseSubArrayDelimiter === '"'){
968
+ throw ConfigurationError.quotedFieldConflict('parseSubArrayDelimiter', '"');
969
+ }
970
+ }
971
+ }
972
+
973
+ /**
974
+ * Check if a line contains quote characters
975
+ * @param {string} line - Line to check
976
+ * @returns {boolean} True if line contains quotes
977
+ * @private
978
+ */
979
+ hasQuotes(line) {
980
+ return line.includes('"');
981
+ }
982
+
983
+ /**
984
+ * Split a CSV record line into fields, respecting quoted fields per RFC 4180.
985
+ * Handles:
986
+ * - Quoted fields with embedded delimiters and newlines
987
+ * - Escaped quotes (double quotes within quoted fields)
988
+ * - Empty quoted fields
989
+ * @param {string} line - A single CSV record line
990
+ * @returns {string[]} Array of field values
991
+ */
992
+ split(line) {
993
+ if (line.length === 0) {
994
+ return [];
995
+ }
996
+
997
+ let fields = [];
998
+ let currentField = '';
999
+ let insideQuotes = false;
1000
+ let delimiter = this.getFieldDelimiter();
1001
+
1002
+ for (let i = 0; i < line.length; i++) {
1003
+ let char = line[i];
1004
+
1005
+ // Handle quote character
1006
+ if (char === QUOTE_CHAR) {
1007
+ if (this.isEscapedQuote(line, i, insideQuotes)) {
1008
+ // Two consecutive quotes inside quoted field = escaped quote
1009
+ currentField += QUOTE_CHAR;
1010
+ i++; // Skip next quote
1011
+ } else if (this.isEmptyQuotedField(line, i, insideQuotes, currentField, delimiter)) {
1012
+ // Empty quoted field: "" at field start before delimiter/end
1013
+ i++; // Skip closing quote
1014
+ } else {
1015
+ // Regular quote: toggle quoted state
1016
+ insideQuotes = !insideQuotes;
1017
+ }
1018
+ } else if (char === delimiter && !insideQuotes) {
1019
+ // Delimiter outside quotes marks field boundary
1020
+ fields.push(currentField);
1021
+ currentField = '';
1022
+ } else {
1023
+ // Regular character (including embedded newlines in quoted fields)
1024
+ currentField += char;
1025
+ }
1026
+ }
1027
+
1028
+ // Add final field
1029
+ fields.push(currentField);
1030
+
1031
+ // Validate matching quotes
1032
+ if (insideQuotes) {
1033
+ throw CsvFormatError.mismatchedQuotes('row');
1034
+ }
1035
+
1036
+ return fields;
1037
+ }
1038
+
1039
+ /**
1040
+ * Check if character at index is an escaped quote (double quote)
1041
+ * Escaped quotes appear as "" within quoted fields per RFC 4180
1042
+ * @param {string} line - Line being parsed
1043
+ * @param {number} index - Character index to check
1044
+ * @param {boolean} insideQuoted - Whether currently inside a quoted field
1045
+ * @returns {boolean} True if character is an escaped quote
1046
+ * @private
1047
+ */
1048
+ isEscapedQuote(line, index, insideQuoted) {
1049
+ return insideQuoted &&
1050
+ index + 1 < line.length &&
1051
+ line[index + 1] === QUOTE_CHAR;
1052
+ }
1053
+
1054
+ /**
1055
+ * Check if this is an empty quoted field: "" before delimiter or end of line
1056
+ * @param {string} line - Line being parsed
1057
+ * @param {number} index - Character index to check
1058
+ * @param {boolean} insideQuoted - Whether currently inside a quoted field
1059
+ * @param {string} currentField - Current field accumulation
1060
+ * @param {string} delimiter - Field delimiter character
1061
+ * @returns {boolean} True if this represents an empty quoted field
1062
+ * @private
1063
+ */
1064
+ isEmptyQuotedField(line, index, insideQuoted, currentField, delimiter) {
1065
+ if (insideQuoted || currentField !== '' || index + 1 >= line.length) {
1066
+ return false;
1067
+ }
1068
+
1069
+ let nextChar = line[index + 1];
1070
+ if (nextChar !== QUOTE_CHAR) {
1071
+ return false; // Not a quote pair
1072
+ }
1073
+
1074
+ let afterQuotes = index + 2;
1075
+ return afterQuotes === line.length || line[afterQuotes] === delimiter;
1076
+ }
1077
+ }
1078
+
1079
+ module.exports = new CsvToJson();
1080
+
1081
+ },{"./util/errors":5,"./util/fileUtils":6,"./util/jsonUtils":7,"./util/stringUtils":8}],5:[function(require,module,exports){
1082
+ 'use strict';
1083
+
1084
+ /**
1085
+ * Custom error classes following clean code principles
1086
+ * Provides clear, actionable error messages with context
1087
+ * @category Error Classes
1088
+ */
1089
+
1090
+ /**
1091
+ * Base class for all CSV parsing errors
1092
+ * Provides consistent error formatting and context
1093
+ * @category Error Classes
1094
+ */
1095
+ class CsvParsingError extends Error {
1096
+ /**
1097
+ * Create a CSV parsing error
1098
+ * @param {string} message - Error message
1099
+ * @param {string} code - Error code for identification
1100
+ * @param {object} context - Additional context information (default: {})
1101
+ */
1102
+ constructor(message, code, context = {}) {
1103
+ super(message);
1104
+ this.name = 'CsvParsingError';
1105
+ this.code = code;
1106
+ this.context = context;
1107
+ Error.captureStackTrace(this, this.constructor);
1108
+ }
1109
+
1110
+ /**
1111
+ * Convert error to formatted string with context information
1112
+ * @returns {string} Formatted error message including context
1113
+ */
1114
+ toString() {
1115
+ let output = `${this.name}: ${this.message}`;
1116
+
1117
+ if (this.context && Object.keys(this.context).length > 0) {
1118
+ output += '\n\nContext:';
1119
+ Object.entries(this.context).forEach(([key, value]) => {
1120
+ output += `\n ${key}: ${this.formatValue(value)}`;
1121
+ });
1122
+ }
1123
+
1124
+ return output;
1125
+ }
1126
+
1127
+ /**
1128
+ * Format a context value for display in error message
1129
+ * @param {unknown} value - Value to format
1130
+ * @returns {string} Formatted value string
1131
+ * @private
1132
+ */
1133
+ formatValue(value) {
1134
+ if (value === null) return 'null';
1135
+ if (value === undefined) return 'undefined';
1136
+ if (typeof value === 'string') return `"${value}"`;
1137
+ if (typeof value === 'object') return JSON.stringify(value);
1138
+ return String(value);
1139
+ }
1140
+ }
1141
+
1142
+ /**
1143
+ * Input validation errors
1144
+ * Thrown when function parameters don't meet expected type or value requirements
1145
+ * @category Error Classes
1146
+ */
1147
+ class InputValidationError extends CsvParsingError {
1148
+ /**
1149
+ * Create an input validation error
1150
+ * @param {string} paramName - Name of the invalid parameter
1151
+ * @param {string} expectedType - Expected type description
1152
+ * @param {string} receivedType - Actual type received
1153
+ * @param {string} details - Additional error details (optional)
1154
+ */
1155
+ constructor(paramName, expectedType, receivedType, details = '') {
1156
+ const message =
1157
+ `Invalid input: Parameter '${paramName}' is required.\n` +
1158
+ `Expected: ${expectedType}\n` +
1159
+ `Received: ${receivedType}${details ? '\n' + details : ''}`;
1160
+
1161
+ super(message, 'INPUT_VALIDATION_ERROR', {
1162
+ parameter: paramName,
1163
+ expectedType,
1164
+ receivedType
1165
+ });
1166
+ this.name = 'InputValidationError';
1167
+ }
1168
+ }
1169
+
1170
+ /**
1171
+ * Configuration-related errors
1172
+ * Thrown when configuration options conflict or are invalid
1173
+ * @category Error Classes
1174
+ */
1175
+ class ConfigurationError extends CsvParsingError {
1176
+ /**
1177
+ * Create a configuration error
1178
+ * @param {string} message - Error message
1179
+ * @param {object} conflictingOptions - Configuration options in conflict (optional)
1180
+ */
1181
+ constructor(message, conflictingOptions = {}) {
1182
+ super(message, 'CONFIGURATION_ERROR', conflictingOptions);
1183
+ this.name = 'ConfigurationError';
1184
+ }
1185
+
1186
+ /**
1187
+ * Create error for quoted field configuration conflict
1188
+ * Occurs when quote character is used as delimiter while quoted fields are enabled
1189
+ * @param {string} optionName - Name of the conflicting option
1190
+ * @param {string} value - Value that causes the conflict
1191
+ * @returns {ConfigurationError} Configured error instance
1192
+ * @static
1193
+ */
1194
+ static quotedFieldConflict(optionName, value) {
1195
+ return new ConfigurationError(
1196
+ `Configuration conflict: supportQuotedField() is enabled, but ${optionName} is set to '${value}'.\n` +
1197
+ `The quote character (") cannot be used as a field delimiter, separator, or sub-array delimiter when quoted field support is active.\n\n` +
1198
+ `Solutions:\n` +
1199
+ ` 1. Use a different character for ${optionName} (e.g., '|', '\\t', ';')\n` +
1200
+ ` 2. Disable supportQuotedField() if your CSV doesn't contain quoted fields\n` +
1201
+ ` 3. Refer to RFC 4180 for proper CSV formatting: https://tools.ietf.org/html/rfc4180`,
1202
+ { optionName, value, conflictingOption: 'supportQuotedField' }
1203
+ );
1204
+ }
1205
+
1206
+ /**
1207
+ * Create error for invalid header index
1208
+ * Occurs when indexHeader() receives non-numeric value
1209
+ * @param {unknown} value - Invalid header index value
1210
+ * @returns {ConfigurationError} Configured error instance
1211
+ * @static
1212
+ */
1213
+ static invalidHeaderIndex(value) {
1214
+ return new ConfigurationError(
1215
+ `Invalid configuration: indexHeader() expects a numeric value.\n` +
1216
+ `Received: ${typeof value} (${value})\n\n` +
1217
+ `Solutions:\n` +
1218
+ ` 1. Ensure indexHeader() receives a number: indexHeader(0), indexHeader(1), etc.\n` +
1219
+ ` 2. Headers are typically found on row 0 (first line)\n` +
1220
+ ` 3. Use indexHeader(2) if headers are on the 3rd line`,
1221
+ { parameterName: 'indexHeader', value, type: typeof value }
1222
+ );
1223
+ }
1224
+ }
1225
+
1226
+ /**
1227
+ * CSV parsing errors with detailed context
1228
+ * Thrown when CSV format is invalid or malformed
1229
+ * @category Error Classes
1230
+ */
1231
+ class CsvFormatError extends CsvParsingError {
1232
+ /**
1233
+ * Create a CSV format error
1234
+ * @param {string} message - Error message
1235
+ * @param {object} context - Additional context information (optional)
1236
+ */
1237
+ constructor(message, context = {}) {
1238
+ super(message, 'CSV_FORMAT_ERROR', context);
1239
+ this.name = 'CsvFormatError';
1240
+ }
1241
+
1242
+ /**
1243
+ * Create error for missing CSV header row
1244
+ * Occurs when no valid header row is found in CSV
1245
+ * @returns {CsvFormatError} Configured error instance
1246
+ * @static
1247
+ */
1248
+ static missingHeader() {
1249
+ return new CsvFormatError(
1250
+ `CSV parsing error: No header row found.\n` +
1251
+ `The CSV file appears to be empty or has no valid header line.\n\n` +
1252
+ `Solutions:\n` +
1253
+ ` 1. Ensure your CSV file contains at least one row (header row)\n` +
1254
+ ` 2. Verify the file is not empty or contains only whitespace\n` +
1255
+ ` 3. Check if you need to use indexHeader(n) to specify a non-standard header row\n` +
1256
+ ` 4. Refer to RFC 4180 for proper CSV format: https://tools.ietf.org/html/rfc4180`
1257
+ );
1258
+ }
1259
+
1260
+ /**
1261
+ * Create error for mismatched quotes in CSV
1262
+ * Occurs when quoted fields are not properly closed
1263
+ * @param {string} location - Where the error occurred (default: 'CSV')
1264
+ * @returns {CsvFormatError} Configured error instance
1265
+ * @static
1266
+ */
1267
+ static mismatchedQuotes(location = 'CSV') {
1268
+ return new CsvFormatError(
1269
+ `CSV parsing error: Mismatched quotes detected in ${location}.\n` +
1270
+ `A quoted field was not properly closed with a matching quote character.\n\n` +
1271
+ `RFC 4180 rules for quoted fields:\n` +
1272
+ ` • Fields containing delimiters or quotes MUST be enclosed in double quotes\n` +
1273
+ ` • To include a quote within a quoted field, use two consecutive quotes: ""\n` +
1274
+ ` • Example: "Smith, John" (name contains comma)\n` +
1275
+ ` • Example: "He said ""Hello""" (text contains quotes)\n\n` +
1276
+ `Solutions:\n` +
1277
+ ` 1. Review your CSV for properly paired quote characters\n` +
1278
+ ` 2. Use double quotes ("") to escape quotes within quoted fields\n` +
1279
+ ` 3. Ensure all commas within field values are inside quotes\n` +
1280
+ ` 4. Enable supportQuotedField(true) if you're using quoted fields`,
1281
+ { location }
1282
+ );
1283
+ }
1284
+ }
1285
+
1286
+ /**
1287
+ * File operation errors
1288
+ * Thrown when file read or write operations fail
1289
+ * @category Error Classes
1290
+ */
1291
+ class FileOperationError extends CsvParsingError {
1292
+ /**
1293
+ * Create a file operation error
1294
+ * @param {string} operation - Type of operation that failed (e.g., 'read', 'write')
1295
+ * @param {string} filePath - Path to the file where operation failed
1296
+ * @param {Error} originalError - The underlying error object from Node.js
1297
+ */
1298
+ constructor(operation, filePath, originalError) {
1299
+ const message =
1300
+ `File operation error: Failed to ${operation} file.\n` +
1301
+ `File path: ${filePath}\n` +
1302
+ `Reason: ${originalError.message}\n\n` +
1303
+ `Solutions:\n` +
1304
+ ` 1. Verify the file path is correct: ${filePath}\n` +
1305
+ ` 2. Check file permissions (read access for input, write access for output)\n` +
1306
+ ` 3. Ensure the directory exists and is writable for output files\n` +
1307
+ ` 4. Verify the file is not in use by another process`;
1308
+
1309
+ super(message, 'FILE_OPERATION_ERROR', {
1310
+ operation,
1311
+ filePath,
1312
+ originalError: originalError.message
1313
+ });
1314
+ this.name = 'FileOperationError';
1315
+ this.originalError = originalError;
1316
+ }
1317
+ }
1318
+
1319
+ /**
1320
+ * JSON validation errors
1321
+ * Thrown when parsed CSV data cannot be converted to valid JSON
1322
+ * @category Error Classes
1323
+ */
1324
+ class JsonValidationError extends CsvParsingError {
1325
+ /**
1326
+ * Create a JSON validation error
1327
+ * @param {string} csvData - The CSV data that failed validation
1328
+ * @param {Error} originalError - The underlying JSON parsing error
1329
+ */
1330
+ constructor(csvData, originalError) {
1331
+ const message =
1332
+ `JSON validation error: The parsed CSV data generated invalid JSON.\n` +
1333
+ `This typically indicates malformed field names or values in the CSV.\n` +
1334
+ `Original error: ${originalError.message}\n\n` +
1335
+ `Solutions:\n` +
1336
+ ` 1. Check that field names are valid JavaScript identifiers (or will be converted safely)\n` +
1337
+ ` 2. Review the CSV data for special characters that aren't properly escaped\n` +
1338
+ ` 3. Enable supportQuotedField(true) for fields containing special characters\n` +
1339
+ ` 4. Verify that formatValueByType() isn't converting values incorrectly`;
1340
+
1341
+ super(message, 'JSON_VALIDATION_ERROR', {
1342
+ originalError: originalError.message,
1343
+ csvPreview: csvData ? csvData.substring(0, 200) : 'N/A'
1344
+ });
1345
+ this.name = 'JsonValidationError';
1346
+ this.originalError = originalError;
1347
+ }
1348
+ }
1349
+
1350
+ /**
1351
+ * Browser-specific errors
1352
+ * Thrown when browser API operations fail
1353
+ * @category Error Classes
1354
+ */
1355
+ class BrowserApiError extends CsvParsingError {
1356
+ /**
1357
+ * Create a browser API error
1358
+ * @param {string} message - Error message
1359
+ * @param {object} context - Additional context information (optional)
1360
+ */
1361
+ constructor(message, context = {}) {
1362
+ super(message, 'BROWSER_API_ERROR', context);
1363
+ this.name = 'BrowserApiError';
1364
+ }
1365
+
1366
+ /**
1367
+ * Create error for unavailable FileReader API
1368
+ * Occurs when browser doesn't support FileReader
1369
+ * @returns {BrowserApiError} Configured error instance
1370
+ * @static
1371
+ */
1372
+ static fileReaderNotAvailable() {
1373
+ return new BrowserApiError(
1374
+ `Browser compatibility error: FileReader API is not available.\n` +
1375
+ `Your browser does not support the FileReader API required for file parsing.\n\n` +
1376
+ `Solutions:\n` +
1377
+ ` 1. Use a modern browser that supports FileReader (Chrome 13+, Firefox 10+, Safari 6+)\n` +
1378
+ ` 2. Consider using csvStringToJson() or csvStringToJsonAsync() for string-based parsing\n` +
1379
+ ` 3. Implement a polyfill or alternative file reading method`
1380
+ );
1381
+ }
1382
+
1383
+ /**
1384
+ * Create error for file parsing failure in browser
1385
+ * Occurs when file read or CSV parse fails
1386
+ * @param {Error} originalError - The underlying error that occurred
1387
+ * @returns {BrowserApiError} Configured error instance
1388
+ * @static
1389
+ */
1390
+ static parseFileError(originalError) {
1391
+ return new BrowserApiError(
1392
+ `Browser file parsing error: Failed to read and parse the file.\n` +
1393
+ `Error details: ${originalError.message}\n\n` +
1394
+ `Solutions:\n` +
1395
+ ` 1. Verify the file is a valid CSV file\n` +
1396
+ ` 2. Check the file encoding (UTF-8 is recommended)\n` +
1397
+ ` 3. Try a smaller file to isolate the issue\n` +
1398
+ ` 4. Check browser console for additional error details`,
1399
+ { originalError: originalError.message }
1400
+ );
1401
+ }
1402
+ }
1403
+
1404
+ module.exports = {
1405
+ CsvParsingError,
1406
+ InputValidationError,
1407
+ ConfigurationError,
1408
+ CsvFormatError,
1409
+ FileOperationError,
1410
+ JsonValidationError,
1411
+ BrowserApiError
1412
+ };
1413
+
1414
+ },{}],6:[function(require,module,exports){
1415
+ 'use strict';
1416
+
1417
+ const fs = require('fs');
1418
+ const { FileOperationError } = require('./errors');
1419
+
1420
+ /**
1421
+ * File I/O utilities for reading and writing CSV/JSON files
1422
+ * Provides both synchronous and asynchronous file operations
1423
+ * @category Utilities
1424
+ */
1425
+ class FileUtils {
1426
+
1427
+ /**
1428
+ * Read a file synchronously with specified encoding
1429
+ * @param {string} fileInputName - Path to file to read
1430
+ * @param {string} encoding - File encoding (e.g., 'utf8', 'latin1')
1431
+ * @returns {string} File contents as string
1432
+ * @throws {FileOperationError} If file read fails
1433
+ */
1434
+ readFile(fileInputName, encoding) {
1435
+ try {
1436
+ return fs.readFileSync(fileInputName, encoding).toString();
1437
+ } catch (error) {
1438
+ throw new FileOperationError('read', fileInputName, error);
1439
+ }
1440
+ }
1441
+
1442
+ /**
1443
+ * Read a file asynchronously with specified encoding
1444
+ * Uses fs.promises when available, falls back to callback-based API
1445
+ * @param {string} fileInputName - Path to file to read
1446
+ * @param {string} encoding - File encoding (default: 'utf8')
1447
+ * @returns {Promise<string>} Promise resolving to file contents
1448
+ * @throws {FileOperationError} If file read fails
1449
+ */
1450
+ readFileAsync(fileInputName, encoding = 'utf8') {
1451
+ // Use fs.promises when available for a Promise-based API
1452
+ if (fs.promises && typeof fs.promises.readFile === 'function') {
1453
+ return fs.promises.readFile(fileInputName, encoding)
1454
+ .then(buf => buf.toString())
1455
+ .catch(error => {
1456
+ throw new FileOperationError('read', fileInputName, error);
1457
+ });
1458
+ }
1459
+ return new Promise((resolve, reject) => {
1460
+ fs.readFile(fileInputName, encoding, (err, data) => {
1461
+ if (err) {
1462
+ reject(new FileOperationError('read', fileInputName, err));
1463
+ return;
1464
+ }
1465
+ resolve(data.toString());
1466
+ });
1467
+ });
1468
+ }
1469
+
1470
+ /**
1471
+ * Write content to a file synchronously
1472
+ * Logs confirmation message to console on success
1473
+ * @param {string} json - Content to write to file
1474
+ * @param {string} fileOutputName - Path to output file
1475
+ * @throws {FileOperationError} If file write fails
1476
+ */
1477
+ writeFile(json, fileOutputName) {
1478
+ fs.writeFile(fileOutputName, json, function (err) {
1479
+ if (err) {
1480
+ throw new FileOperationError('write', fileOutputName, err);
1481
+ } else {
1482
+ console.log('File saved: ' + fileOutputName);
1483
+ }
1484
+ });
1485
+ }
1486
+
1487
+ /**
1488
+ * Write content to a file asynchronously
1489
+ * Uses fs.promises when available, falls back to callback-based API
1490
+ * @param {string} json - Content to write to file
1491
+ * @param {string} fileOutputName - Path to output file
1492
+ * @returns {Promise<void>} Promise that resolves when write completes
1493
+ * @throws {FileOperationError} If file write fails
1494
+ */
1495
+ writeFileAsync(json, fileOutputName) {
1496
+ if (fs.promises && typeof fs.promises.writeFile === 'function') {
1497
+ return fs.promises.writeFile(fileOutputName, json)
1498
+ .catch(error => {
1499
+ throw new FileOperationError('write', fileOutputName, error);
1500
+ });
1501
+ }
1502
+ return new Promise((resolve, reject) => {
1503
+ fs.writeFile(fileOutputName, json, (err) => {
1504
+ if (err) return reject(new FileOperationError('write', fileOutputName, err));
1505
+ resolve();
1506
+ });
1507
+ });
1508
+ }
1509
+
1510
+ }
1511
+ module.exports = new FileUtils();
1512
+
1513
+ },{"./errors":5,"fs":2}],7:[function(require,module,exports){
1514
+ 'use strict';
1515
+
1516
+ const { JsonValidationError } = require('./errors');
1517
+
1518
+ /**
1519
+ * JSON validation utilities
1520
+ * @category Utilities
1521
+ */
1522
+ class JsonUtil {
1523
+
1524
+ /**
1525
+ * Validate that a string is valid JSON
1526
+ * @param {string} json - JSON string to validate
1527
+ * @throws {JsonValidationError} If JSON is invalid
1528
+ */
1529
+ validateJson(json) {
1530
+ try {
1531
+ JSON.parse(json);
1532
+ } catch (err) {
1533
+ throw new JsonValidationError(json, err);
1534
+ }
1535
+ }
1536
+
1537
+ }
1538
+
1539
+ module.exports = new JsonUtil();
1540
+ },{"./errors":5}],8:[function(require,module,exports){
1541
+ 'use strict';
1542
+
1543
+ /**
1544
+ * String processing utilities for CSV parsing
1545
+ * @category Utilities
1546
+ */
1547
+ class StringUtils {
1548
+ // Regular expressions as constants for better maintainability
1549
+ static PATTERNS = {
1550
+ INTEGER: /^-?\d+$/,
1551
+ FLOAT: /^-?\d*\.\d+$/,
1552
+ WHITESPACE: /\s/g
1553
+ };
1554
+
1555
+ static BOOLEAN_VALUES = {
1556
+ TRUE: 'true',
1557
+ FALSE: 'false'
1558
+ };
1559
+
1560
+ /**
1561
+ * Removes whitespace from property names based on configuration
1562
+ * @param {boolean} shouldTrimAll - If true, removes all whitespace, otherwise only trims edges
1563
+ * @param {string} propertyName - The property name to process
1564
+ * @returns {string} The processed property name
1565
+ */
1566
+ trimPropertyName(shouldTrimAll, propertyName) {
1567
+ if (!propertyName) {
1568
+ return '';
1569
+ }
1570
+ return shouldTrimAll ?
1571
+ propertyName.replace(StringUtils.PATTERNS.WHITESPACE, '') :
1572
+ propertyName.trim();
1573
+ }
1574
+
1575
+ /**
1576
+ * Converts a string value to its appropriate type while preserving data integrity
1577
+ * @param {string} value - The input value to convert
1578
+ * @returns {string|number|boolean} The converted value
1579
+ */
1580
+ getValueFormatByType(value) {
1581
+ if (this.isEmpty(value)) {
1582
+ return String();
1583
+ }
1584
+
1585
+ if (this.isBoolean(value)) {
1586
+ return this.convertToBoolean(value);
1587
+ }
1588
+
1589
+ if (this.isInteger(value)) {
1590
+ return this.convertInteger(value);
1591
+ }
1592
+
1593
+ if (this.isFloat(value)) {
1594
+ return this.convertFloat(value);
1595
+ }
1596
+
1597
+ return String(value);
1598
+ }
1599
+
1600
+ /**
1601
+ * Checks if a value array contains any non-empty values
1602
+ * @param {Array} values - Array to check for content
1603
+ * @returns {boolean} True if array has any non-empty values
1604
+ */
1605
+ hasContent(values = []) {
1606
+ return Array.isArray(values) &&
1607
+ values.some(value => Boolean(value));
1608
+ }
1609
+
1610
+ // Private helper methods for type checking and conversion
1611
+ /**
1612
+ * Check if a value is empty (undefined or empty string)
1613
+ * @param {unknown} value - Value to check
1614
+ * @returns {boolean} True if value is undefined or empty string
1615
+ * @private
1616
+ */
1617
+ isEmpty(value) {
1618
+ return value === undefined || value === '';
1619
+ }
1620
+
1621
+ /**
1622
+ * Check if a value is a boolean string ('true' or 'false', case-insensitive)
1623
+ * @param {string} value - Value to check
1624
+ * @returns {boolean} True if value is 'true' or 'false'
1625
+ * @private
1626
+ */
1627
+ isBoolean(value) {
1628
+ const normalizedValue = value.toLowerCase();
1629
+ return normalizedValue === StringUtils.BOOLEAN_VALUES.TRUE ||
1630
+ normalizedValue === StringUtils.BOOLEAN_VALUES.FALSE;
1631
+ }
1632
+
1633
+ /**
1634
+ * Check if a value is an integer string (with optional leading minus sign)
1635
+ * @param {string} value - Value to check
1636
+ * @returns {boolean} True if value matches integer pattern
1637
+ * @private
1638
+ */
1639
+ isInteger(value) {
1640
+ return StringUtils.PATTERNS.INTEGER.test(value);
1641
+ }
1642
+
1643
+ /**
1644
+ * Check if a value is a float string (decimal number with optional leading minus sign)
1645
+ * @param {string} value - Value to check
1646
+ * @returns {boolean} True if value matches float pattern
1647
+ * @private
1648
+ */
1649
+ isFloat(value) {
1650
+ return StringUtils.PATTERNS.FLOAT.test(value);
1651
+ }
1652
+
1653
+ /**
1654
+ * Check if a numeric string has a leading zero (e.g., '01' or '-01')
1655
+ * Leading zeros indicate the value should be kept as a string to preserve formatting
1656
+ * @param {string} value - Numeric string value to check
1657
+ * @returns {boolean} True if value has a leading zero
1658
+ * @private
1659
+ */
1660
+ hasLeadingZero(value) {
1661
+ const isPositiveWithLeadingZero = value.length > 1 && value[0] === '0';
1662
+ const isNegativeWithLeadingZero = value.length > 2 && value[0] === '-' && value[1] === '0';
1663
+ return isPositiveWithLeadingZero || isNegativeWithLeadingZero;
1664
+ }
1665
+
1666
+ /**
1667
+ * Convert a boolean string to native boolean value
1668
+ * Safely converts 'true' to true and 'false' to false
1669
+ * @param {string} value - Boolean string ('true' or 'false')
1670
+ * @returns {boolean} Native boolean value
1671
+ * @private
1672
+ */
1673
+ convertToBoolean(value) {
1674
+ return JSON.parse(value.toLowerCase());
1675
+ }
1676
+
1677
+ /**
1678
+ * Convert an integer string to number or keep as string if it has leading zeros
1679
+ * Preserves leading zeros in strings (e.g., '007' stays as string)
1680
+ * @param {string} value - Integer string to convert
1681
+ * @returns {number|string} Number if safe, otherwise string value
1682
+ * @private
1683
+ */
1684
+ convertInteger(value) {
1685
+ if (this.hasLeadingZero(value)) {
1686
+ return String(value);
1687
+ }
1688
+
1689
+ const num = Number(value);
1690
+ return Number.isSafeInteger(num) ? num : String(value);
1691
+ }
1692
+
1693
+ /**
1694
+ * Convert a float string to number or keep as string if conversion is unsafe
1695
+ * @param {string} value - Float string to convert
1696
+ * @returns {number|string} Number if finite and valid, otherwise string value
1697
+ * @private
1698
+ */
1699
+ convertFloat(value) {
1700
+ const num = Number(value);
1701
+ return Number.isFinite(num) ? num : String(value);
1702
+ }
1703
+ }
1704
+
1705
+ module.exports = new StringUtils();
1706
+
1707
+ },{}]},{},[1])(1)
1708
+ });