convert-csv-to-json 4.1.0 → 4.3.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/.eslintignore +3 -0
- package/.eslintrc.json +48 -0
- package/.github/workflows/ci-cd.yml +28 -2
- package/SECURITY.md +2 -0
- package/docs/api/BrowserApi.html +2435 -0
- package/docs/api/BrowserApiError.html +522 -0
- package/docs/api/ConfigurationError.html +594 -0
- package/docs/api/CsvFormatError.html +530 -0
- package/docs/api/CsvParsingError.html +384 -0
- package/docs/api/CsvToJson.html +3136 -0
- package/docs/api/CsvToJsonAsync.html +2672 -0
- package/docs/api/FileOperationError.html +270 -0
- package/docs/api/FileUtils.html +1012 -0
- package/docs/api/InputValidationError.html +293 -0
- package/docs/api/JsonUtil.html +340 -0
- package/docs/api/JsonValidationError.html +247 -0
- package/docs/api/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/docs/api/fonts/OpenSans-Bold-webfont.svg +1830 -0
- package/docs/api/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/docs/api/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/docs/api/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
- package/docs/api/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/docs/api/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/docs/api/fonts/OpenSans-Italic-webfont.svg +1830 -0
- package/docs/api/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/docs/api/fonts/OpenSans-Light-webfont.eot +0 -0
- package/docs/api/fonts/OpenSans-Light-webfont.svg +1831 -0
- package/docs/api/fonts/OpenSans-Light-webfont.woff +0 -0
- package/docs/api/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/docs/api/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
- package/docs/api/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/docs/api/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/docs/api/fonts/OpenSans-Regular-webfont.svg +1831 -0
- package/docs/api/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/docs/api/global.html +3315 -0
- package/docs/api/index.html +326 -0
- package/docs/api/index.js.html +341 -0
- package/docs/api/scripts/linenumber.js +25 -0
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/api/scripts/prettify/lang-css.js +2 -0
- package/docs/api/scripts/prettify/prettify.js +28 -0
- package/docs/api/src_browserApi.js.html +271 -0
- package/docs/api/src_csvToJson.js.html +605 -0
- package/docs/api/src_csvToJsonAsync.js.html +244 -0
- package/docs/api/src_util_errors.js.html +374 -0
- package/docs/api/src_util_fileUtils.js.html +147 -0
- package/docs/api/src_util_jsonUtils.js.html +75 -0
- package/docs/api/src_util_stringUtils.js.html +212 -0
- package/docs/api/styles/jsdoc-default.css +358 -0
- package/docs/api/styles/prettify-jsdoc.css +111 -0
- package/docs/api/styles/prettify-tomorrow.css +132 -0
- package/index.js +109 -32
- package/jsdoc.json +17 -0
- package/package.json +10 -3
- package/src/browserApi.js +96 -4
- package/src/csvToJson.js +163 -2
- package/src/csvToJsonAsync.js +74 -14
- package/src/util/errors.js +96 -0
- package/src/util/fileUtils.js +34 -0
- package/src/util/jsonUtils.js +8 -0
- package/src/util/stringUtils.js +51 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>JSDoc: Source: src/csvToJson.js</title>
|
|
6
|
+
|
|
7
|
+
<script src="scripts/prettify/prettify.js"> </script>
|
|
8
|
+
<script src="scripts/prettify/lang-css.js"> </script>
|
|
9
|
+
<!--[if lt IE 9]>
|
|
10
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
11
|
+
<![endif]-->
|
|
12
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
|
13
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
|
14
|
+
</head>
|
|
15
|
+
|
|
16
|
+
<body>
|
|
17
|
+
|
|
18
|
+
<div id="main">
|
|
19
|
+
|
|
20
|
+
<h1 class="page-title">Source: src/csvToJson.js</h1>
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
<section>
|
|
28
|
+
<article>
|
|
29
|
+
<pre class="prettyprint source linenums"><code>/* globals FileOperationError */
|
|
30
|
+
"use strict";
|
|
31
|
+
|
|
32
|
+
const fileUtils = require('./util/fileUtils');
|
|
33
|
+
const stringUtils = require('./util/stringUtils');
|
|
34
|
+
const jsonUtils = require('./util/jsonUtils');
|
|
35
|
+
const {
|
|
36
|
+
ConfigurationError,
|
|
37
|
+
CsvFormatError,
|
|
38
|
+
JsonValidationError
|
|
39
|
+
} = require('./util/errors');
|
|
40
|
+
|
|
41
|
+
const DEFAULT_FIELD_DELIMITER = ",";
|
|
42
|
+
const QUOTE_CHAR = '"';
|
|
43
|
+
const CRLF = '\r\n';
|
|
44
|
+
const LF = '\n';
|
|
45
|
+
const CR = '\r';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Main CSV to JSON converter class
|
|
49
|
+
* Provides chainable API for configuring and converting CSV data
|
|
50
|
+
*/
|
|
51
|
+
class CsvToJson {
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Enable or disable automatic type formatting for values
|
|
55
|
+
* When enabled, numeric strings are converted to numbers, 'true'/'false' to booleans
|
|
56
|
+
* @param {boolean} active - Whether to format values by type
|
|
57
|
+
* @returns {this} For method chaining
|
|
58
|
+
*/
|
|
59
|
+
formatValueByType(active) {
|
|
60
|
+
this.printValueFormatByType = active;
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Enable or disable support for RFC 4180 quoted fields
|
|
66
|
+
* When enabled, fields wrapped in double quotes can contain delimiters and newlines
|
|
67
|
+
* @param {boolean} active - Whether to support quoted fields
|
|
68
|
+
* @returns {this} For method chaining
|
|
69
|
+
*/
|
|
70
|
+
supportQuotedField(active) {
|
|
71
|
+
this.isSupportQuotedField = active;
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Set the field delimiter character
|
|
77
|
+
* @param {string} delimiter - Character(s) to use as field separator (default: ',')
|
|
78
|
+
* @returns {this} For method chaining
|
|
79
|
+
*/
|
|
80
|
+
fieldDelimiter(delimiter) {
|
|
81
|
+
this.delimiter = delimiter;
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Configure whitespace handling in header field names
|
|
87
|
+
* @param {boolean} active - If true, removes all whitespace from header names; if false, only trims edges
|
|
88
|
+
* @returns {this} For method chaining
|
|
89
|
+
*/
|
|
90
|
+
trimHeaderFieldWhiteSpace(active) {
|
|
91
|
+
this.isTrimHeaderFieldWhiteSpace = active;
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Set the row index where CSV headers are located
|
|
97
|
+
* @param {number} indexHeaderValue - Zero-based row index containing headers
|
|
98
|
+
* @returns {this} For method chaining
|
|
99
|
+
* @throws {ConfigurationError} If not a valid number
|
|
100
|
+
*/
|
|
101
|
+
indexHeader(indexHeaderValue) {
|
|
102
|
+
if (isNaN(indexHeaderValue)) {
|
|
103
|
+
throw ConfigurationError.invalidHeaderIndex(indexHeaderValue);
|
|
104
|
+
}
|
|
105
|
+
this.indexHeaderValue = indexHeaderValue;
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Configure sub-array parsing for special field values
|
|
112
|
+
* Fields bracketed by delimiter and containing separator are parsed into arrays
|
|
113
|
+
* @param {string} delimiter - Bracket character (default: '*')
|
|
114
|
+
* @param {string} separator - Item separator within brackets (default: ',')
|
|
115
|
+
* @returns {this} For method chaining
|
|
116
|
+
* @example
|
|
117
|
+
* // Input: "*val1,val2,val3*"
|
|
118
|
+
* // Output: ["val1", "val2", "val3"]
|
|
119
|
+
* .parseSubArray('*', ',')
|
|
120
|
+
*/
|
|
121
|
+
parseSubArray(delimiter = '*',separator = ',') {
|
|
122
|
+
this.parseSubArrayDelimiter = delimiter;
|
|
123
|
+
this.parseSubArraySeparator = separator;
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Set file encoding for reading CSV files
|
|
129
|
+
* @param {string} encoding - Node.js supported encoding (e.g., 'utf8', 'latin1', 'ascii')
|
|
130
|
+
* @returns {this} For method chaining
|
|
131
|
+
*/
|
|
132
|
+
encoding(encoding){
|
|
133
|
+
this.encoding = encoding;
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Sets a mapper function to transform each row after conversion
|
|
139
|
+
* @param {Function} mapperFn - Function that receives (row, index) and returns transformed row or null to filter out
|
|
140
|
+
* @returns {this} - For method chaining
|
|
141
|
+
*/
|
|
142
|
+
mapRows(mapperFn) {
|
|
143
|
+
if (typeof mapperFn !== 'function') {
|
|
144
|
+
throw new TypeError('mapperFn must be a function');
|
|
145
|
+
}
|
|
146
|
+
this.rowMapper = mapperFn;
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Read a CSV file and write the parsed JSON to an output file
|
|
152
|
+
* @param {string} fileInputName - Path to input CSV file
|
|
153
|
+
* @param {string} fileOutputName - Path to output JSON file
|
|
154
|
+
* @throws {FileOperationError} If file read or write fails
|
|
155
|
+
* @throws {CsvFormatError} If CSV is malformed
|
|
156
|
+
*/
|
|
157
|
+
generateJsonFileFromCsv(fileInputName, fileOutputName) {
|
|
158
|
+
let jsonStringified = this.getJsonFromCsvStringified(fileInputName);
|
|
159
|
+
fileUtils.writeFile(jsonStringified, fileOutputName);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Read a CSV file and return parsed data as stringified JSON
|
|
164
|
+
* @param {string} fileInputName - Path to input CSV file
|
|
165
|
+
* @returns {string} JSON stringified array of objects
|
|
166
|
+
* @throws {FileOperationError} If file read fails
|
|
167
|
+
* @throws {CsvFormatError} If CSV is malformed
|
|
168
|
+
* @throws {JsonValidationError} If JSON generation fails
|
|
169
|
+
* @example
|
|
170
|
+
* const csvToJson = require('convert-csv-to-json');
|
|
171
|
+
* const jsonString = csvToJson.getJsonFromCsvStringified('resource/input.csv');
|
|
172
|
+
* console.log(jsonString);
|
|
173
|
+
*/
|
|
174
|
+
getJsonFromCsvStringified(fileInputName) {
|
|
175
|
+
let json = this.getJsonFromCsv(fileInputName);
|
|
176
|
+
let jsonStringified = JSON.stringify(json, undefined, 1);
|
|
177
|
+
jsonUtils.validateJson(jsonStringified);
|
|
178
|
+
return jsonStringified;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Read a CSV file and return parsed data as JSON array of objects
|
|
183
|
+
* @param {string} fileInputName - Path to input CSV file
|
|
184
|
+
* @returns {Array<object>} Array of objects representing CSV rows
|
|
185
|
+
* @throws {FileOperationError} If file read fails
|
|
186
|
+
* @throws {CsvFormatError} If CSV is malformed
|
|
187
|
+
* @example
|
|
188
|
+
* const csvToJson = require('convert-csv-to-json');
|
|
189
|
+
* const rows = csvToJson.getJsonFromCsv('resource/input.csv');
|
|
190
|
+
* console.log(rows);
|
|
191
|
+
*/
|
|
192
|
+
getJsonFromCsv(fileInputName) {
|
|
193
|
+
let parsedCsv = fileUtils.readFile(fileInputName, this.encoding);
|
|
194
|
+
return this.csvToJson(parsedCsv);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Parse CSV string content and return as JSON array of objects
|
|
199
|
+
* @param {string} csvString - CSV content as string
|
|
200
|
+
* @returns {Array<object>} Array of objects representing CSV rows
|
|
201
|
+
* @throws {CsvFormatError} If CSV is malformed
|
|
202
|
+
* @example
|
|
203
|
+
* const csvToJson = require('convert-csv-to-json');
|
|
204
|
+
* const rows = csvToJson.csvStringToJson('name,age\nAlice,30');
|
|
205
|
+
* console.log(rows); // [{ name: 'Alice', age: '30' }]
|
|
206
|
+
*/
|
|
207
|
+
csvStringToJson(csvString) {
|
|
208
|
+
return this.csvToJson(csvString);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Parse CSV string content and return as stringified JSON
|
|
213
|
+
* @param {string} csvString - CSV content as string
|
|
214
|
+
* @returns {string} JSON stringified array of objects
|
|
215
|
+
* @throws {CsvFormatError} If CSV is malformed
|
|
216
|
+
* @throws {JsonValidationError} If JSON generation fails
|
|
217
|
+
* @example
|
|
218
|
+
* const csvToJson = require('convert-csv-to-json');
|
|
219
|
+
* const jsonString = csvToJson.csvStringToJsonStringified('name,age\nAlice,30');
|
|
220
|
+
* console.log(jsonString);
|
|
221
|
+
*/
|
|
222
|
+
csvStringToJsonStringified(csvString) {
|
|
223
|
+
let json = this.csvStringToJson(csvString);
|
|
224
|
+
let jsonStringified = JSON.stringify(json, undefined, 1);
|
|
225
|
+
jsonUtils.validateJson(jsonStringified);
|
|
226
|
+
return jsonStringified;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Core CSV parsing logic - converts CSV string to JSON array
|
|
231
|
+
* Handles quoted fields per RFC 4180 when configured
|
|
232
|
+
* Applies row mapping and filtering when configured
|
|
233
|
+
* @param {string} parsedCsv - Raw CSV content as string
|
|
234
|
+
* @returns {Array<object>} Array of objects with CSV data
|
|
235
|
+
* @private
|
|
236
|
+
*/
|
|
237
|
+
csvToJson(parsedCsv) {
|
|
238
|
+
this.validateInputConfig();
|
|
239
|
+
|
|
240
|
+
// Parse CSV into individual records, respecting quoted fields that may contain newlines
|
|
241
|
+
let records = this.parseRecords(parsedCsv);
|
|
242
|
+
|
|
243
|
+
let fieldDelimiter = this.getFieldDelimiter();
|
|
244
|
+
let index = this.getIndexHeader();
|
|
245
|
+
let headers;
|
|
246
|
+
|
|
247
|
+
// Find the header row
|
|
248
|
+
while (index < records.length) {
|
|
249
|
+
if (this.isSupportQuotedField) {
|
|
250
|
+
headers = this.split(records[index]);
|
|
251
|
+
} else {
|
|
252
|
+
headers = records[index].split(fieldDelimiter);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (stringUtils.hasContent(headers)) {
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
index++;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!headers) {
|
|
262
|
+
throw CsvFormatError.missingHeader();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
let jsonResult = [];
|
|
266
|
+
for (let i = (index + 1); i < records.length; i++) {
|
|
267
|
+
let currentLine;
|
|
268
|
+
if (this.isSupportQuotedField) {
|
|
269
|
+
currentLine = this.split(records[i]);
|
|
270
|
+
} else {
|
|
271
|
+
currentLine = records[i].split(fieldDelimiter);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (stringUtils.hasContent(currentLine)) {
|
|
275
|
+
let row = this.buildJsonResult(headers, currentLine);
|
|
276
|
+
|
|
277
|
+
// Apply row mapper if defined
|
|
278
|
+
if (this.rowMapper) {
|
|
279
|
+
row = this.rowMapper(row, i - (index + 1)); // Pass row and 0-based row index
|
|
280
|
+
// If mapper returns null/undefined, skip this row (allows filtering)
|
|
281
|
+
if (row != null) {
|
|
282
|
+
jsonResult.push(row);
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
jsonResult.push(row);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return jsonResult;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Parse CSV content into individual records, respecting quoted fields that may span multiple lines.
|
|
294
|
+
* RFC 4180 compliant parsing - handles quoted fields that may contain newlines.
|
|
295
|
+
* @param {string} csvContent - The raw CSV content
|
|
296
|
+
* @returns {string[]} Array of record strings
|
|
297
|
+
*/
|
|
298
|
+
parseRecords(csvContent) {
|
|
299
|
+
let records = [];
|
|
300
|
+
let currentRecord = '';
|
|
301
|
+
let insideQuotes = false;
|
|
302
|
+
let i = 0;
|
|
303
|
+
|
|
304
|
+
while (i < csvContent.length) {
|
|
305
|
+
let char = csvContent[i];
|
|
306
|
+
|
|
307
|
+
// Handle quote characters
|
|
308
|
+
if (char === QUOTE_CHAR) {
|
|
309
|
+
if (insideQuotes && i + 1 < csvContent.length && csvContent[i + 1] === QUOTE_CHAR) {
|
|
310
|
+
// Escaped quote: two consecutive quotes = single quote representation
|
|
311
|
+
currentRecord += QUOTE_CHAR + QUOTE_CHAR;
|
|
312
|
+
i += 2;
|
|
313
|
+
} else {
|
|
314
|
+
// Toggle quote state
|
|
315
|
+
insideQuotes = !insideQuotes;
|
|
316
|
+
currentRecord += char;
|
|
317
|
+
i++;
|
|
318
|
+
}
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Handle line endings (only outside quoted fields)
|
|
323
|
+
if (!insideQuotes) {
|
|
324
|
+
let lineEndingLength = this.getLineEndingLength(csvContent, i);
|
|
325
|
+
if (lineEndingLength > 0) {
|
|
326
|
+
records.push(currentRecord);
|
|
327
|
+
currentRecord = '';
|
|
328
|
+
i += lineEndingLength;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Regular character
|
|
334
|
+
currentRecord += char;
|
|
335
|
+
i++;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Add the last record if not empty
|
|
339
|
+
if (currentRecord.length > 0) {
|
|
340
|
+
records.push(currentRecord);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Validate matching quotes
|
|
344
|
+
if (insideQuotes) {
|
|
345
|
+
throw CsvFormatError.mismatchedQuotes('CSV');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return records;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Get the length of line ending at current position (CRLF=2, LF=1, CR=1, or 0)
|
|
353
|
+
* @param {string} content - CSV content
|
|
354
|
+
* @param {number} index - Current index to check
|
|
355
|
+
* @returns {number} Length of line ending (0 if none)
|
|
356
|
+
*/
|
|
357
|
+
getLineEndingLength(content, index) {
|
|
358
|
+
if (content.slice(index, index + 2) === CRLF) {
|
|
359
|
+
return 2;
|
|
360
|
+
}
|
|
361
|
+
if (content[index] === LF) {
|
|
362
|
+
return 1;
|
|
363
|
+
}
|
|
364
|
+
if (content[index] === CR && content[index + 1] !== LF) {
|
|
365
|
+
return 1;
|
|
366
|
+
}
|
|
367
|
+
return 0;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Get the configured field delimiter, or default if not set
|
|
372
|
+
* @returns {string} Field delimiter character
|
|
373
|
+
* @private
|
|
374
|
+
*/
|
|
375
|
+
getFieldDelimiter() {
|
|
376
|
+
if (this.delimiter) {
|
|
377
|
+
return this.delimiter;
|
|
378
|
+
}
|
|
379
|
+
return DEFAULT_FIELD_DELIMITER;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Get the configured header row index, or default (0) if not set
|
|
384
|
+
* @returns {number} Header row index
|
|
385
|
+
* @private
|
|
386
|
+
*/
|
|
387
|
+
getIndexHeader(){
|
|
388
|
+
if(this.indexHeaderValue !== null && !isNaN(this.indexHeaderValue)){
|
|
389
|
+
return this.indexHeaderValue;
|
|
390
|
+
}
|
|
391
|
+
return 0;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Build a JSON object from headers and field values
|
|
396
|
+
* Applies type formatting and sub-array parsing as configured
|
|
397
|
+
* @param {string[]} headers - Array of header field names
|
|
398
|
+
* @param {string[]} currentLine - Array of field values
|
|
399
|
+
* @returns {object} JSON object with header names as keys
|
|
400
|
+
* @private
|
|
401
|
+
*/
|
|
402
|
+
buildJsonResult(headers, currentLine) {
|
|
403
|
+
let jsonObject = {};
|
|
404
|
+
for (let j = 0; j < headers.length; j++) {
|
|
405
|
+
let propertyName = stringUtils.trimPropertyName(this.isTrimHeaderFieldWhiteSpace, headers[j]);
|
|
406
|
+
let value = currentLine[j];
|
|
407
|
+
|
|
408
|
+
if(this.isParseSubArray(value)){
|
|
409
|
+
value = this.buildJsonSubArray(value);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (this.printValueFormatByType && !Array.isArray(value)) {
|
|
413
|
+
value = stringUtils.getValueFormatByType(currentLine[j]);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
jsonObject[propertyName] = value;
|
|
417
|
+
}
|
|
418
|
+
return jsonObject;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Parse a field value into a sub-array using configured delimiter and separator
|
|
423
|
+
* @param {string} value - Field value to parse
|
|
424
|
+
* @returns {Array<string|number|boolean>} Array of parsed values
|
|
425
|
+
* @private
|
|
426
|
+
*/
|
|
427
|
+
buildJsonSubArray(value) {
|
|
428
|
+
let extractedValues = value.substring(
|
|
429
|
+
value.indexOf(this.parseSubArrayDelimiter) + 1,
|
|
430
|
+
value.lastIndexOf(this.parseSubArrayDelimiter)
|
|
431
|
+
);
|
|
432
|
+
extractedValues.trim();
|
|
433
|
+
value = extractedValues.split(this.parseSubArraySeparator);
|
|
434
|
+
if(this.printValueFormatByType){
|
|
435
|
+
for(let i=0; i < value.length; i++){
|
|
436
|
+
value[i] = stringUtils.getValueFormatByType(value[i]);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return value;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Check if a field value should be parsed as a sub-array
|
|
444
|
+
* @param {string} value - Field value to check
|
|
445
|
+
* @returns {boolean} True if value is bracketed with sub-array delimiter
|
|
446
|
+
* @private
|
|
447
|
+
*/
|
|
448
|
+
isParseSubArray(value){
|
|
449
|
+
if(this.parseSubArrayDelimiter){
|
|
450
|
+
if (value && (value.indexOf(this.parseSubArrayDelimiter) === 0 && value.lastIndexOf(this.parseSubArrayDelimiter) === (value.length - 1))) {
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Validate configuration for conflicts and incompatibilities
|
|
459
|
+
* @throws {ConfigurationError} If incompatible options are set
|
|
460
|
+
* @private
|
|
461
|
+
*/
|
|
462
|
+
validateInputConfig(){
|
|
463
|
+
if(this.isSupportQuotedField) {
|
|
464
|
+
if(this.getFieldDelimiter() === '"'){
|
|
465
|
+
throw ConfigurationError.quotedFieldConflict('fieldDelimiter', '"');
|
|
466
|
+
}
|
|
467
|
+
if(this.parseSubArraySeparator === '"'){
|
|
468
|
+
throw ConfigurationError.quotedFieldConflict('parseSubArraySeparator', '"');
|
|
469
|
+
}
|
|
470
|
+
if(this.parseSubArrayDelimiter === '"'){
|
|
471
|
+
throw ConfigurationError.quotedFieldConflict('parseSubArrayDelimiter', '"');
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Check if a line contains quote characters
|
|
478
|
+
* @param {string} line - Line to check
|
|
479
|
+
* @returns {boolean} True if line contains quotes
|
|
480
|
+
* @private
|
|
481
|
+
*/
|
|
482
|
+
hasQuotes(line) {
|
|
483
|
+
return line.includes('"');
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Split a CSV record line into fields, respecting quoted fields per RFC 4180.
|
|
488
|
+
* Handles:
|
|
489
|
+
* - Quoted fields with embedded delimiters and newlines
|
|
490
|
+
* - Escaped quotes (double quotes within quoted fields)
|
|
491
|
+
* - Empty quoted fields
|
|
492
|
+
* @param {string} line - A single CSV record line
|
|
493
|
+
* @returns {string[]} Array of field values
|
|
494
|
+
*/
|
|
495
|
+
split(line) {
|
|
496
|
+
if (line.length === 0) {
|
|
497
|
+
return [];
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
let fields = [];
|
|
501
|
+
let currentField = '';
|
|
502
|
+
let insideQuotes = false;
|
|
503
|
+
let delimiter = this.getFieldDelimiter();
|
|
504
|
+
|
|
505
|
+
for (let i = 0; i < line.length; i++) {
|
|
506
|
+
let char = line[i];
|
|
507
|
+
|
|
508
|
+
// Handle quote character
|
|
509
|
+
if (char === QUOTE_CHAR) {
|
|
510
|
+
if (this.isEscapedQuote(line, i, insideQuotes)) {
|
|
511
|
+
// Two consecutive quotes inside quoted field = escaped quote
|
|
512
|
+
currentField += QUOTE_CHAR;
|
|
513
|
+
i++; // Skip next quote
|
|
514
|
+
} else if (this.isEmptyQuotedField(line, i, insideQuotes, currentField, delimiter)) {
|
|
515
|
+
// Empty quoted field: "" at field start before delimiter/end
|
|
516
|
+
i++; // Skip closing quote
|
|
517
|
+
} else {
|
|
518
|
+
// Regular quote: toggle quoted state
|
|
519
|
+
insideQuotes = !insideQuotes;
|
|
520
|
+
}
|
|
521
|
+
} else if (char === delimiter && !insideQuotes) {
|
|
522
|
+
// Delimiter outside quotes marks field boundary
|
|
523
|
+
fields.push(currentField);
|
|
524
|
+
currentField = '';
|
|
525
|
+
} else {
|
|
526
|
+
// Regular character (including embedded newlines in quoted fields)
|
|
527
|
+
currentField += char;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Add final field
|
|
532
|
+
fields.push(currentField);
|
|
533
|
+
|
|
534
|
+
// Validate matching quotes
|
|
535
|
+
if (insideQuotes) {
|
|
536
|
+
throw CsvFormatError.mismatchedQuotes('row');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return fields;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Check if character at index is an escaped quote (double quote)
|
|
544
|
+
* Escaped quotes appear as "" within quoted fields per RFC 4180
|
|
545
|
+
* @param {string} line - Line being parsed
|
|
546
|
+
* @param {number} index - Character index to check
|
|
547
|
+
* @param {boolean} insideQuoted - Whether currently inside a quoted field
|
|
548
|
+
* @returns {boolean} True if character is an escaped quote
|
|
549
|
+
* @private
|
|
550
|
+
*/
|
|
551
|
+
isEscapedQuote(line, index, insideQuoted) {
|
|
552
|
+
return insideQuoted &&
|
|
553
|
+
index + 1 < line.length &&
|
|
554
|
+
line[index + 1] === QUOTE_CHAR;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Check if this is an empty quoted field: "" before delimiter or end of line
|
|
559
|
+
* @param {string} line - Line being parsed
|
|
560
|
+
* @param {number} index - Character index to check
|
|
561
|
+
* @param {boolean} insideQuoted - Whether currently inside a quoted field
|
|
562
|
+
* @param {string} currentField - Current field accumulation
|
|
563
|
+
* @param {string} delimiter - Field delimiter character
|
|
564
|
+
* @returns {boolean} True if this represents an empty quoted field
|
|
565
|
+
* @private
|
|
566
|
+
*/
|
|
567
|
+
isEmptyQuotedField(line, index, insideQuoted, currentField, delimiter) {
|
|
568
|
+
if (insideQuoted || currentField !== '' || index + 1 >= line.length) {
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
let nextChar = line[index + 1];
|
|
573
|
+
if (nextChar !== QUOTE_CHAR) {
|
|
574
|
+
return false; // Not a quote pair
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
let afterQuotes = index + 2;
|
|
578
|
+
return afterQuotes === line.length || line[afterQuotes] === delimiter;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
module.exports = new CsvToJson();
|
|
583
|
+
</code></pre>
|
|
584
|
+
</article>
|
|
585
|
+
</section>
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
</div>
|
|
591
|
+
|
|
592
|
+
<nav>
|
|
593
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="BrowserApi.html">BrowserApi</a></li><li><a href="BrowserApiError.html">BrowserApiError</a></li><li><a href="ConfigurationError.html">ConfigurationError</a></li><li><a href="CsvFormatError.html">CsvFormatError</a></li><li><a href="CsvParsingError.html">CsvParsingError</a></li><li><a href="CsvToJson.html">CsvToJson</a></li><li><a href="CsvToJsonAsync.html">CsvToJsonAsync</a></li><li><a href="FileOperationError.html">FileOperationError</a></li><li><a href="FileUtils.html">FileUtils</a></li><li><a href="InputValidationError.html">InputValidationError</a></li><li><a href="JsonUtil.html">JsonUtil</a></li><li><a href="JsonValidationError.html">JsonValidationError</a></li></ul><h3>Global</h3><ul><li><a href="global.html#asciiEncoding">asciiEncoding</a></li><li><a href="global.html#base64Encoding">base64Encoding</a></li><li><a href="global.html#browser">browser</a></li><li><a href="global.html#csvStringToJson">csvStringToJson</a></li><li><a href="global.html#csvStringToJsonStringified">csvStringToJsonStringified</a></li><li><a href="global.html#csvToJsonAsync">csvToJsonAsync</a></li><li><a href="global.html#customEncoding">customEncoding</a></li><li><a href="global.html#fieldDelimiter">fieldDelimiter</a></li><li><a href="global.html#formatValueByType">formatValueByType</a></li><li><a href="global.html#generateJsonFileFromCsv">generateJsonFileFromCsv</a></li><li><a href="global.html#getJsonFromCsv">getJsonFromCsv</a></li><li><a href="global.html#hexEncoding">hexEncoding</a></li><li><a href="global.html#indexHeader">indexHeader</a></li><li><a href="global.html#latin1Encoding">latin1Encoding</a></li><li><a href="global.html#mapRows">mapRows</a></li><li><a href="global.html#parseSubArray">parseSubArray</a></li><li><a href="global.html#supportQuotedField">supportQuotedField</a></li><li><a href="global.html#trimHeaderFieldWhiteSpace">trimHeaderFieldWhiteSpace</a></li><li><a href="global.html#ucs2Encoding">ucs2Encoding</a></li><li><a href="global.html#utf16leEncoding">utf16leEncoding</a></li><li><a href="global.html#utf8Encoding">utf8Encoding</a></li></ul>
|
|
594
|
+
</nav>
|
|
595
|
+
|
|
596
|
+
<br class="clear">
|
|
597
|
+
|
|
598
|
+
<footer>
|
|
599
|
+
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.5</a> on Fri Mar 20 2026 14:48:29 GMT+0000 (Coordinated Universal Time)
|
|
600
|
+
</footer>
|
|
601
|
+
|
|
602
|
+
<script> prettyPrint(); </script>
|
|
603
|
+
<script src="scripts/linenumber.js"> </script>
|
|
604
|
+
</body>
|
|
605
|
+
</html>
|