jtcsv 2.1.0 → 2.1.3

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 (40) hide show
  1. package/README.md +63 -17
  2. package/bin/jtcsv.js +1013 -117
  3. package/csv-to-json.js +385 -311
  4. package/examples/simple-usage.js +2 -3
  5. package/index.d.ts +288 -5
  6. package/index.js +23 -0
  7. package/json-to-csv.js +130 -89
  8. package/package.json +47 -19
  9. package/plugins/README.md +146 -2
  10. package/plugins/hono/README.md +25 -0
  11. package/plugins/hono/index.d.ts +12 -0
  12. package/plugins/hono/index.js +36 -0
  13. package/plugins/hono/package.json +35 -0
  14. package/plugins/nestjs/README.md +33 -0
  15. package/plugins/nestjs/index.d.ts +25 -0
  16. package/plugins/nestjs/index.js +77 -0
  17. package/plugins/nestjs/package.json +37 -0
  18. package/plugins/nuxt/README.md +25 -0
  19. package/plugins/nuxt/index.js +21 -0
  20. package/plugins/nuxt/package.json +35 -0
  21. package/plugins/nuxt/runtime/composables/useJtcsv.js +6 -0
  22. package/plugins/nuxt/runtime/plugin.js +6 -0
  23. package/plugins/remix/README.md +26 -0
  24. package/plugins/remix/index.d.ts +16 -0
  25. package/plugins/remix/index.js +62 -0
  26. package/plugins/remix/package.json +35 -0
  27. package/plugins/sveltekit/README.md +28 -0
  28. package/plugins/sveltekit/index.d.ts +17 -0
  29. package/plugins/sveltekit/index.js +54 -0
  30. package/plugins/sveltekit/package.json +33 -0
  31. package/plugins/trpc/README.md +22 -0
  32. package/plugins/trpc/index.d.ts +7 -0
  33. package/plugins/trpc/index.js +32 -0
  34. package/plugins/trpc/package.json +34 -0
  35. package/src/core/delimiter-cache.js +186 -0
  36. package/src/core/transform-hooks.js +350 -0
  37. package/src/engines/fast-path-engine.js +829 -340
  38. package/src/formats/tsv-parser.js +336 -0
  39. package/src/index-with-plugins.js +36 -14
  40. package/cli-tui.js +0 -5
@@ -238,8 +238,8 @@ try {
238
238
  console.log(' examples/plugin-excel-exporter.js');
239
239
  } catch (error) {
240
240
  console.log('⚠️ Полная интеграция требует дополнительных зависимостей');
241
- console.log('Установите exceljs для демонстрации Excel плагина:');
242
- console.log(' npm install exceljs --save-optional');
241
+ console.log('Установите @jtcsv/excel и exceljs для демонстрации Excel плагина:');
242
+ console.log(' npm install @jtcsv/excel exceljs');
243
243
  }
244
244
 
245
245
  // ============================================================================
@@ -277,4 +277,3 @@ console.log('✨ JTCSV 2.1.0 - Next Generation JSON/CSV Converter ✨');
277
277
  console.log('✨'.repeat(30));
278
278
 
279
279
 
280
-
package/index.d.ts CHANGED
@@ -44,6 +44,10 @@ declare module 'jtcsv' {
44
44
  parseBooleans?: boolean;
45
45
  /** Maximum number of rows to process (optional, no limit by default) */
46
46
  maxRows?: number;
47
+ /** Enable fast-path parsing (default: true) */
48
+ useFastPath?: boolean;
49
+ /** Fast-path output mode (default: 'objects') */
50
+ fastPathMode?: 'objects' | 'compact' | 'stream';
47
51
  }
48
52
 
49
53
  // JSON save interfaces
@@ -71,6 +75,99 @@ declare module 'jtcsv' {
71
75
  schema?: Record<string, any>;
72
76
  }
73
77
 
78
+ // NDJSON interfaces
79
+ export interface NdjsonOptions {
80
+ /** Buffer size for streaming (default: 64KB) */
81
+ bufferSize?: number;
82
+ /** Maximum line length (default: 10MB) */
83
+ maxLineLength?: number;
84
+ /** Error handler callback */
85
+ onError?: (error: Error, line: string, lineNumber: number) => void;
86
+ /** JSON stringify replacer function */
87
+ replacer?: (key: string, value: any) => any;
88
+ /** JSON stringify space (indentation) */
89
+ space?: number | string;
90
+ /** Filter function for rows */
91
+ filter?: (obj: Record<string, any>, index: number) => boolean;
92
+ /** Transform function for rows */
93
+ transform?: (obj: Record<string, any>, index: number) => any;
94
+ }
95
+
96
+ export interface NdjsonToCsvStreamOptions {
97
+ /** Delimiter for CSV output (default: ',') */
98
+ delimiter?: string;
99
+ /** Include headers row (default: true) */
100
+ includeHeaders?: boolean;
101
+ }
102
+
103
+ export interface CsvToNdjsonStreamOptions {
104
+ /** Delimiter for CSV input (default: ',') */
105
+ delimiter?: string;
106
+ /** Whether CSV has headers (default: true) */
107
+ hasHeaders?: boolean;
108
+ }
109
+
110
+ export interface NdjsonStats {
111
+ /** Total lines in NDJSON */
112
+ totalLines: number;
113
+ /** Valid JSON lines */
114
+ validLines: number;
115
+ /** Lines with JSON parsing errors */
116
+ errorLines: number;
117
+ /** Total bytes */
118
+ totalBytes: number;
119
+ /** Success rate percentage */
120
+ successRate: number;
121
+ /** Array of parsing errors */
122
+ errors: Array<{
123
+ line: number;
124
+ error: string;
125
+ content: string;
126
+ }>;
127
+ }
128
+
129
+ // TSV interfaces
130
+ export interface TsvOptions extends JsonToCsvOptions, CsvToJsonOptions {
131
+ /** Always use tab as delimiter for TSV */
132
+ delimiter?: '\t';
133
+ /** Disable auto-detection for TSV */
134
+ autoDetect?: false;
135
+ }
136
+
137
+ export interface ValidateTsvOptions {
138
+ /** Require consistent column count (default: true) */
139
+ requireConsistentColumns?: boolean;
140
+ /** Disallow empty fields (default: false) */
141
+ disallowEmptyFields?: boolean;
142
+ }
143
+
144
+ export interface TsvValidationResult {
145
+ /** Whether TSV is valid */
146
+ valid: boolean;
147
+ /** Error message if invalid */
148
+ error?: string;
149
+ /** Validation statistics */
150
+ stats: {
151
+ /** Total lines */
152
+ totalLines: number;
153
+ /** Total columns in first line */
154
+ totalColumns: number;
155
+ /** Minimum columns across all lines */
156
+ minColumns: number;
157
+ /** Maximum columns across all lines */
158
+ maxColumns: number;
159
+ /** Whether all lines have same column count */
160
+ consistentColumns: boolean;
161
+ };
162
+ /** Array of validation errors */
163
+ errors?: Array<{
164
+ line?: number;
165
+ error: string;
166
+ details?: any;
167
+ fields?: number[];
168
+ }>;
169
+ }
170
+
74
171
  // Error classes
75
172
  export class JtcsvError extends Error {
76
173
  code: string;
@@ -182,10 +279,31 @@ declare module 'jtcsv' {
182
279
  * @throws {LimitError} If row limit exceeded
183
280
  */
184
281
  export function csvToJson(
185
- csv: string,
282
+ csv: string,
283
+ options: CsvToJsonOptions & { fastPathMode: 'stream' }
284
+ ): AsyncGenerator<Record<string, any> | any[]>;
285
+
286
+ export function csvToJson(
287
+ csv: string,
288
+ options: CsvToJsonOptions & { fastPathMode: 'compact' }
289
+ ): any[][];
290
+
291
+ export function csvToJson(
292
+ csv: string,
186
293
  options?: CsvToJsonOptions
187
294
  ): Record<string, any>[];
188
295
 
296
+ /**
297
+ * Convert CSV string to JSON rows as async iterator
298
+ * @param csv CSV string to convert
299
+ * @param options Conversion options
300
+ * @returns Async generator yielding rows
301
+ */
302
+ export function csvToJsonIterator(
303
+ csv: string,
304
+ options?: CsvToJsonOptions
305
+ ): AsyncGenerator<Record<string, any> | any[]>;
306
+
189
307
  /**
190
308
  * Read CSV file and convert it to JSON array
191
309
  * @param filePath Path to CSV file
@@ -360,8 +478,173 @@ declare module 'jtcsv' {
360
478
  * @returns Writable stream that collects data
361
479
  */
362
480
  export function createJsonCollectorStream(): Writable;
481
+
482
+ // NDJSON format support
483
+
484
+ /**
485
+ * Convert JSON array to NDJSON string
486
+ * @param data Array of objects to convert
487
+ * @param options NDJSON options
488
+ * @returns NDJSON string
489
+ */
490
+ export function jsonToNdjson<T extends Record<string, any>>(
491
+ data: T[],
492
+ options?: NdjsonOptions
493
+ ): string;
494
+
495
+ /**
496
+ * Convert NDJSON string to JSON array
497
+ * @param ndjsonString NDJSON string
498
+ * @param options NDJSON options
499
+ * @returns JSON array
500
+ */
501
+ export function ndjsonToJson(
502
+ ndjsonString: string,
503
+ options?: NdjsonOptions
504
+ ): Record<string, any>[];
505
+
506
+ /**
507
+ * Parse NDJSON stream as async iterator
508
+ * @param input ReadableStream or string input
509
+ * @param options NDJSON options
510
+ * @returns Async generator of JSON objects
511
+ */
512
+ export function parseNdjsonStream(
513
+ input: ReadableStream | string,
514
+ options?: NdjsonOptions
515
+ ): AsyncGenerator<Record<string, any>>;
516
+
517
+ /**
518
+ * Create TransformStream for converting NDJSON to CSV
519
+ * @param options Conversion options
520
+ * @returns TransformStream
521
+ */
522
+ export function createNdjsonToCsvStream(
523
+ options?: NdjsonToCsvStreamOptions
524
+ ): TransformStream;
525
+
526
+ /**
527
+ * Create TransformStream for converting CSV to NDJSON
528
+ * @param options Conversion options
529
+ * @returns TransformStream
530
+ */
531
+ export function createCsvToNdjsonStream(
532
+ options?: CsvToNdjsonStreamOptions
533
+ ): TransformStream;
534
+
535
+ /**
536
+ * Get statistics for NDJSON data
537
+ * @param input NDJSON string or ReadableStream
538
+ * @returns Promise with NDJSON statistics
539
+ */
540
+ export function getNdjsonStats(
541
+ input: string | ReadableStream
542
+ ): Promise<NdjsonStats>;
543
+
544
+ // TSV format support
545
+
546
+ /**
547
+ * Convert JSON array to TSV string
548
+ * @param data Array of objects to convert
549
+ * @param options TSV options
550
+ * @returns TSV string
551
+ */
552
+ export function jsonToTsv<T extends Record<string, any>>(
553
+ data: T[],
554
+ options?: TsvOptions
555
+ ): string;
556
+
557
+ /**
558
+ * Convert TSV string to JSON array
559
+ * @param tsvString TSV string
560
+ * @param options TSV options
561
+ * @returns JSON array
562
+ */
563
+ export function tsvToJson(
564
+ tsvString: string,
565
+ options?: TsvOptions
566
+ ): Record<string, any>[];
567
+
568
+ /**
569
+ * Check if string is likely TSV format
570
+ * @param sample Sample data string
571
+ * @returns True if likely TSV format
572
+ */
573
+ export function isTsv(sample: string): boolean;
574
+
575
+ /**
576
+ * Validate TSV string structure
577
+ * @param tsvString TSV string to validate
578
+ * @param options Validation options
579
+ * @returns Validation result
580
+ */
581
+ export function validateTsv(
582
+ tsvString: string,
583
+ options?: ValidateTsvOptions
584
+ ): TsvValidationResult;
585
+
586
+ /**
587
+ * Read TSV file and convert to JSON array
588
+ * @param filePath Path to TSV file
589
+ * @param options TSV options
590
+ * @returns Promise with JSON array
591
+ */
592
+ export function readTsvAsJson(
593
+ filePath: string,
594
+ options?: TsvOptions
595
+ ): Promise<Record<string, any>[]>;
596
+
597
+ /**
598
+ * Synchronously read TSV file and convert to JSON array
599
+ * @param filePath Path to TSV file
600
+ * @param options TSV options
601
+ * @returns JSON array
602
+ */
603
+ export function readTsvAsJsonSync(
604
+ filePath: string,
605
+ options?: TsvOptions
606
+ ): Record<string, any>[];
607
+
608
+ /**
609
+ * Save JSON data as TSV file
610
+ * @param data Array of objects to save
611
+ * @param filePath Output file path
612
+ * @param options TSV options
613
+ * @returns Promise that resolves when file is saved
614
+ */
615
+ export function saveAsTsv<T extends Record<string, any>>(
616
+ data: T[],
617
+ filePath: string,
618
+ options?: TsvOptions
619
+ ): Promise<void>;
620
+
621
+ /**
622
+ * Synchronously save JSON data as TSV file
623
+ * @param data Array of objects to save
624
+ * @param filePath Output file path
625
+ * @param options TSV options
626
+ */
627
+ export function saveAsTsvSync<T extends Record<string, any>>(
628
+ data: T[],
629
+ filePath: string,
630
+ options?: TsvOptions
631
+ ): void;
632
+
633
+ /**
634
+ * Create TransformStream for converting JSON to TSV
635
+ * @param options TSV options
636
+ * @returns TransformStream
637
+ */
638
+ export function createJsonToTsvStream(
639
+ options?: TsvOptions
640
+ ): TransformStream;
641
+
642
+ /**
643
+ * Create TransformStream for converting TSV to JSON
644
+ * @param options TSV options
645
+ * @returns TransformStream
646
+ */
647
+ export function createTsvToJsonStream(
648
+ options?: TsvOptions
649
+ ): TransformStream;
363
650
  }
364
-
365
-
366
-
367
-
package/index.js CHANGED
@@ -7,6 +7,8 @@ const errorsModule = require('./errors');
7
7
  const jsonSaveModule = require('./json-save');
8
8
  const streamJsonToCsvModule = require('./stream-json-to-csv');
9
9
  const streamCsvToJsonModule = require('./stream-csv-to-json');
10
+ const ndjsonParser = require('./src/formats/ndjson-parser');
11
+ const tsvParser = require('./src/formats/tsv-parser');
10
12
 
11
13
  // Combine all exports
12
14
  module.exports = {
@@ -19,6 +21,7 @@ module.exports = {
19
21
 
20
22
  // CSV to JSON functions
21
23
  csvToJson: csvToJsonModule.csvToJson,
24
+ csvToJsonIterator: csvToJsonModule.csvToJsonIterator,
22
25
  readCsvAsJson: csvToJsonModule.readCsvAsJson,
23
26
  readCsvAsJsonSync: csvToJsonModule.readCsvAsJsonSync,
24
27
  autoDetectDelimiter: csvToJsonModule.autoDetectDelimiter,
@@ -40,6 +43,26 @@ module.exports = {
40
43
  createCsvFileToJsonStream: streamCsvToJsonModule.createCsvFileToJsonStream,
41
44
  createJsonCollectorStream: streamCsvToJsonModule.createJsonCollectorStream,
42
45
 
46
+ // NDJSON format support
47
+ jsonToNdjson: ndjsonParser.toNdjson,
48
+ ndjsonToJson: ndjsonParser.fromNdjson,
49
+ parseNdjsonStream: ndjsonParser.parseStream,
50
+ createNdjsonToCsvStream: ndjsonParser.createNdjsonToCsvStream,
51
+ createCsvToNdjsonStream: ndjsonParser.createCsvToNdjsonStream,
52
+ getNdjsonStats: ndjsonParser.getStats,
53
+
54
+ // TSV format support
55
+ jsonToTsv: tsvParser.jsonToTsv,
56
+ tsvToJson: tsvParser.tsvToJson,
57
+ isTsv: tsvParser.isTsv,
58
+ validateTsv: tsvParser.validateTsv,
59
+ readTsvAsJson: tsvParser.readTsvAsJson,
60
+ readTsvAsJsonSync: tsvParser.readTsvAsJsonSync,
61
+ saveAsTsv: tsvParser.saveAsTsv,
62
+ saveAsTsvSync: tsvParser.saveAsTsvSync,
63
+ createJsonToTsvStream: tsvParser.createJsonToTsvStream,
64
+ createTsvToJsonStream: tsvParser.createTsvToJsonStream,
65
+
43
66
  // Error classes
44
67
  ...errorsModule
45
68
  };
package/json-to-csv.js CHANGED
@@ -135,35 +135,68 @@ function jsonToCsv(data, options = {}) {
135
135
  );
136
136
  }
137
137
 
138
- // Get all unique keys from all objects
139
- const allKeys = new Set();
140
- data.forEach((item) => {
141
- if (!item || typeof item !== 'object') {
142
- return;
143
- }
144
- Object.keys(item).forEach(key => allKeys.add(key));
145
- });
146
-
147
- // Convert Set to Array
148
- const originalKeys = Array.from(allKeys);
149
-
150
- // Apply rename map to create header names
151
- const headers = originalKeys.map(key => renameMap[key] || key);
152
-
153
- // Create a reverse mapping from new header to original key
154
- const reverseRenameMap = {};
155
- originalKeys.forEach((key, index) => {
156
- reverseRenameMap[headers[index]] = key;
157
- });
158
-
159
- // Apply template ordering if provided
160
- let finalHeaders = headers;
161
- if (Object.keys(template).length > 0) {
162
- // Create template headers with renaming applied
163
- const templateHeaders = Object.keys(template).map(key => renameMap[key] || key);
164
- const extraHeaders = headers.filter(h => !templateHeaders.includes(h));
165
- finalHeaders = [...templateHeaders, ...extraHeaders];
166
- }
138
+ // Get all unique keys from all objects with minimal allocations.
139
+ const allKeys = new Set();
140
+ const originalKeys = [];
141
+ for (let i = 0; i < data.length; i++) {
142
+ const item = data[i];
143
+ if (!item || typeof item !== 'object') {
144
+ continue;
145
+ }
146
+ for (const key in item) {
147
+ if (Object.prototype.hasOwnProperty.call(item, key) && !allKeys.has(key)) {
148
+ allKeys.add(key);
149
+ originalKeys.push(key);
150
+ }
151
+ }
152
+ }
153
+
154
+ const hasRenameMap = Object.keys(renameMap).length > 0;
155
+ const hasTemplate = Object.keys(template).length > 0;
156
+
157
+ // Apply rename map to create header names.
158
+ let headers = originalKeys;
159
+ let reverseRenameMap = null;
160
+ if (hasRenameMap) {
161
+ headers = new Array(originalKeys.length);
162
+ reverseRenameMap = {};
163
+ for (let i = 0; i < originalKeys.length; i++) {
164
+ const key = originalKeys[i];
165
+ const header = renameMap[key] || key;
166
+ headers[i] = header;
167
+ reverseRenameMap[header] = key;
168
+ }
169
+ }
170
+
171
+ // Apply template ordering if provided.
172
+ let finalHeaders = headers;
173
+ if (hasTemplate) {
174
+ const templateKeys = Object.keys(template);
175
+ const templateHeaders = hasRenameMap
176
+ ? templateKeys.map(key => renameMap[key] || key)
177
+ : templateKeys;
178
+ const templateHeaderSet = new Set(templateHeaders);
179
+ const extraHeaders = [];
180
+ for (let i = 0; i < headers.length; i++) {
181
+ const header = headers[i];
182
+ if (!templateHeaderSet.has(header)) {
183
+ extraHeaders.push(header);
184
+ }
185
+ }
186
+ finalHeaders = templateHeaders.concat(extraHeaders);
187
+ }
188
+
189
+ const finalKeys = new Array(finalHeaders.length);
190
+ if (hasRenameMap) {
191
+ for (let i = 0; i < finalHeaders.length; i++) {
192
+ const header = finalHeaders[i];
193
+ finalKeys[i] = reverseRenameMap[header] || header;
194
+ }
195
+ } else {
196
+ for (let i = 0; i < finalHeaders.length; i++) {
197
+ finalKeys[i] = finalHeaders[i];
198
+ }
199
+ }
167
200
 
168
201
  /**
169
202
  * Escapes a value for CSV format with CSV injection protection
@@ -172,67 +205,75 @@ function jsonToCsv(data, options = {}) {
172
205
  * @param {*} value - The value to escape
173
206
  * @returns {string} Escaped CSV value
174
207
  */
175
- const escapeValue = (value) => {
176
- if (value === null || value === undefined || value === '') {
177
- return '';
178
- }
179
-
180
- const stringValue = String(value);
181
-
182
- // CSV Injection protection - escape formulas if enabled
183
- let escapedValue = stringValue;
184
- if (preventCsvInjection && /^[=+\-@]/.test(stringValue)) {
185
- // Prepend single quote to prevent formula execution in Excel
186
- escapedValue = "'" + stringValue;
187
- }
188
-
189
- // RFC 4180 compliance: fields containing line breaks, double quotes, or commas must be quoted
190
- const needsQuoting = rfc4180Compliant
191
- ? (escapedValue.includes(delimiter) ||
192
- escapedValue.includes('"') ||
193
- escapedValue.includes('\n') ||
194
- escapedValue.includes('\r'))
195
- : (escapedValue.includes(delimiter) ||
196
- escapedValue.includes('"') ||
197
- escapedValue.includes('\n') ||
198
- escapedValue.includes('\r'));
199
-
200
- if (needsQuoting) {
201
- // RFC 4180: If double-quotes are used to enclose fields, then a double-quote
202
- // appearing inside a field must be escaped by preceding it with another double quote.
203
- return `"${escapedValue.replace(/"/g, '""')}"`;
204
- }
205
-
206
- return escapedValue;
207
- };
208
+ const quoteRegex = /"/g;
209
+ const delimiterCode = delimiter.charCodeAt(0);
210
+
211
+ const escapeValue = (value) => {
212
+ if (value === null || value === undefined || value === '') {
213
+ return '';
214
+ }
215
+
216
+ let stringValue = value;
217
+ if (typeof stringValue !== 'string') {
218
+ stringValue = String(stringValue);
219
+ }
220
+
221
+ // CSV Injection protection - escape formulas if enabled
222
+ let escapedValue = stringValue;
223
+ if (preventCsvInjection) {
224
+ const firstCharCode = stringValue.charCodeAt(0);
225
+ if (firstCharCode === 61 || firstCharCode === 43 || firstCharCode === 45 || firstCharCode === 64) {
226
+ escapedValue = "'" + stringValue;
227
+ }
228
+ }
229
+
230
+ let needsQuoting = false;
231
+ let hasQuote = false;
232
+ for (let i = 0; i < escapedValue.length; i++) {
233
+ const code = escapedValue.charCodeAt(i);
234
+ if (code === 34) {
235
+ hasQuote = true;
236
+ needsQuoting = true;
237
+ } else if (code === delimiterCode || code === 10 || code === 13) {
238
+ needsQuoting = true;
239
+ }
240
+ }
241
+
242
+ if (needsQuoting) {
243
+ const quotedValue = hasQuote ? escapedValue.replace(quoteRegex, '""') : escapedValue;
244
+ return `"${quotedValue}"`;
245
+ }
246
+
247
+ return escapedValue;
248
+ };
208
249
 
209
- // Build CSV rows
210
- const rows = [];
211
-
212
- // Add headers row if requested
213
- if (includeHeaders && finalHeaders.length > 0) {
214
- rows.push(finalHeaders.join(delimiter));
215
- }
216
-
217
- // Add data rows
218
- for (const item of data) {
219
- if (!item || typeof item !== 'object') {
220
- continue;
221
- }
222
-
223
- const row = finalHeaders.map(header => {
224
- // Get the original key for this header
225
- const originalKey = reverseRenameMap[header] || header;
226
- const value = item[originalKey];
227
- return escapeValue(value);
228
- }).join(delimiter);
229
-
230
- rows.push(row);
231
- }
232
-
233
- // RFC 4180: Each record is located on a separate line, delimited by a line break (CRLF)
234
- const lineEnding = rfc4180Compliant ? '\r\n' : '\n';
235
- return rows.join(lineEnding);
250
+ // Build CSV rows.
251
+ const rows = [];
252
+ const columnCount = finalHeaders.length;
253
+
254
+ // Add headers row if requested.
255
+ if (includeHeaders && columnCount > 0) {
256
+ rows.push(finalHeaders.join(delimiter));
257
+ }
258
+
259
+ // Add data rows.
260
+ const rowValues = new Array(columnCount);
261
+ for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
262
+ const item = data[rowIndex];
263
+ if (!item || typeof item !== 'object') {
264
+ continue;
265
+ }
266
+
267
+ for (let i = 0; i < columnCount; i++) {
268
+ rowValues[i] = escapeValue(item[finalKeys[i]]);
269
+ }
270
+
271
+ rows.push(rowValues.join(delimiter));
272
+ }
273
+
274
+ // RFC 4180: Each record is located on a separate line, delimited by a line break (CRLF).
275
+ const lineEnding = rfc4180Compliant ? '\r\n' : '\n';
276
+ return rows.join(lineEnding);
236
277
  }, 'PARSE_FAILED', { function: 'jsonToCsv' });
237
278
  }
238
279