@@ -16,12 +16,13 @@ namespace CodeOfChaos.Parsers.Csv;
16
16
/// Provides functionality to parse CSV files into various collection types.
17
17
/// </summary>
18
18
public class CsvParser ( CsvParserConfig config ) : ICsvParser {
19
- protected readonly ConcurrentDictionary < Type , PropertyInfo [ ] > PropertyCache = new ( ) ;
20
19
protected readonly ConcurrentDictionary < Type , string [ ] > HeaderCache = new ( ) ;
21
-
22
- // -----------------------------------------------------------------------------------------------------------------
23
- // Constructors
24
- // -----------------------------------------------------------------------------------------------------------------
20
+ protected readonly ConcurrentDictionary < Type , PropertyInfo [ ] > PropertyCache = new ( ) ;
21
+ /// <inheritdoc />
22
+ public void ClearCaches ( ) {
23
+ PropertyCache . Clear ( ) ;
24
+ HeaderCache . Clear ( ) ;
25
+ }
25
26
/// <summary>
26
27
/// Creates an instance of <see cref="CsvParser" /> using the specified configuration action.
27
28
/// </summary>
@@ -38,15 +39,50 @@ public static CsvParser FromConfig(Action<CsvParserConfig> configAction) {
38
39
configAction ( config ) ;
39
40
return new CsvParser ( config ) ;
40
41
}
41
-
42
42
/// <summary>
43
- /// Creates an instance of <see cref="CsvParser" /> using the default configuration.
43
+ /// Creates an instance of <see cref="CsvParser" /> using the default configuration.
44
44
/// </summary>
45
45
/// <returns>
46
- /// A new instance of <see cref="CsvParser" /> initialized with the default settings, ready
47
- /// to parse CSV data.
46
+ /// A new instance of <see cref="CsvParser" /> initialized with the default settings, ready
47
+ /// to parse CSV data.
48
48
/// </returns>
49
49
public static CsvParser FromDefaultConfig ( ) => new ( new CsvParserConfig ( ) ) ;
50
+ protected PropertyInfo [ ] GetCsvProperties < T > ( ) {
51
+ Type type = typeof ( T ) ;
52
+ if ( PropertyCache . TryGetValue ( type , out PropertyInfo [ ] ? propertyInfos ) ) return propertyInfos ;
53
+
54
+ PropertyInfo [ ] propertyInfosArray = type . GetProperties ( ) . ToArray ( ) ;
55
+ PropertyCache [ type ] = propertyInfosArray ;
56
+ return propertyInfosArray ;
57
+ }
58
+ protected string [ ] GetCsvHeaders < T > ( ) {
59
+ Type type = typeof ( T ) ;
60
+ if ( HeaderCache . TryGetValue ( type , out string [ ] ? headers ) ) return headers ;
61
+
62
+ string [ ] headersArray = GetCsvProperties < T > ( )
63
+ . Select ( p => {
64
+ if ( p . GetCustomAttribute < CsvColumnAttribute > ( ) is not { } attribute )
65
+ return config . UseLowerCaseHeaders ? p . Name . ToLowerInvariant ( ) : p . Name ;
66
+
67
+ return config . UseLowerCaseHeaders
68
+ ? attribute . NameLowerInvariant
69
+ : attribute . Name ;
70
+ } )
71
+ . ToArray ( ) ;
72
+
73
+ HeaderCache [ type ] = headersArray ;
74
+ return headersArray ;
75
+ }
76
+ protected IEnumerable < string > GetCsvValues < T > ( T ? obj ) {
77
+ if ( obj is null ) return [ ] ;
78
+
79
+ return GetCsvProperties < T > ( )
80
+ . Select ( p => p . GetValue ( obj ) ? . ToString ( ) ?? string . Empty ) ;
81
+ }
82
+
83
+ // -----------------------------------------------------------------------------------------------------------------
84
+ // Constructors
85
+ // -----------------------------------------------------------------------------------------------------------------
50
86
51
87
// -----------------------------------------------------------------------------------------------------------------
52
88
// Input Methods
@@ -173,6 +209,7 @@ public async ValueTask<List<T>> ToListAsync<T>(TextReader reader, CancellationTo
173
209
await foreach ( Dictionary < string , string ? > item in FromTextReaderToDictionaryAsync ( reader , ct ) ) {
174
210
results . Add ( item ) ;
175
211
}
212
+
176
213
return results . ToArray ( ) ;
177
214
}
178
215
@@ -184,7 +221,7 @@ public async ValueTask<List<T>> ToListAsync<T>(TextReader reader, CancellationTo
184
221
await foreach ( Dictionary < string , string ? > item in FromTextReaderToDictionaryAsync ( reader , ct ) ) {
185
222
results . Add ( item ) ;
186
223
}
187
-
224
+
188
225
results . TrimExcess ( ) ;
189
226
return results . ToArray ( ) ;
190
227
}
@@ -205,7 +242,7 @@ public async ValueTask<List<T>> ToListAsync<T>(TextReader reader, CancellationTo
205
242
await foreach ( Dictionary < string , string ? > item in FromTextReaderToDictionaryAsync ( reader , ct ) ) {
206
243
results . Add ( item ) ;
207
244
}
208
-
245
+
209
246
results . TrimExcess ( ) ;
210
247
return results ;
211
248
}
@@ -217,7 +254,7 @@ public async ValueTask<List<T>> ToListAsync<T>(TextReader reader, CancellationTo
217
254
await foreach ( Dictionary < string , string ? > item in FromTextReaderToDictionaryAsync ( reader , ct ) ) {
218
255
results . Add ( item ) ;
219
256
}
220
-
257
+
221
258
results . TrimExcess ( ) ;
222
259
return results ;
223
260
}
@@ -355,14 +392,14 @@ private async IAsyncEnumerable<T> FromTextReaderAsync<T>(TextReader reader, [Enu
355
392
}
356
393
357
394
batch . Clear ( ) ;
358
- ct . ThrowIfCancellationRequested ( ) ; // After a batch is done, check if the cancellation token was requested
395
+ ct . ThrowIfCancellationRequested ( ) ; // After a batch is done, check if the cancellation token was requested
359
396
if ( line == null ) break ;
360
397
}
361
398
}
362
399
363
400
private void SetPropertyFromCsvColumn < T > ( T ? value , string [ ] headerColumns , string [ ] values ) where T : class , new ( ) {
364
401
if ( value is null ) return ;
365
-
402
+
366
403
foreach ( PropertyInfo prop in GetCsvProperties < T > ( ) ) {
367
404
int columnIndex = Attribute . GetCustomAttribute ( prop , typeof ( CsvColumnAttribute ) ) is CsvColumnAttribute attribute
368
405
? Array . IndexOf ( headerColumns , attribute . Name )
@@ -401,6 +438,7 @@ private async IAsyncEnumerable<T> FromTextReaderAsync<T>(TextReader reader, [Enu
401
438
string value = values [ j ] ;
402
439
dict [ headerColumns [ j ] ] = value . IsNotNullOrEmpty ( ) ? value : null ;
403
440
}
441
+
404
442
batch . Add ( dict ) ;
405
443
}
406
444
@@ -431,6 +469,7 @@ private async IAsyncEnumerable<T> FromTextReaderAsync<T>(TextReader reader, [Enu
431
469
string value = values [ j ] ;
432
470
dict [ headerColumns [ j ] ] = value . IsNotNullOrEmpty ( ) ? value : null ;
433
471
}
472
+
434
473
batch . Add ( dict ) ;
435
474
}
436
475
@@ -439,12 +478,11 @@ private async IAsyncEnumerable<T> FromTextReaderAsync<T>(TextReader reader, [Enu
439
478
}
440
479
441
480
batch . Clear ( ) ;
442
- ct . ThrowIfCancellationRequested ( ) ; // After a batch is done, check if the cancellation token was requested
481
+ ct . ThrowIfCancellationRequested ( ) ; // After a batch is done, check if the cancellation token was requested
443
482
if ( line == null ) break ;
444
483
}
445
484
}
446
485
#endregion
447
-
448
486
#region Generic Type Writer
449
487
private void FromDataToTextWriter < T > ( TextWriter writer , IEnumerable < T > data ) {
450
488
// Write header row
@@ -457,6 +495,7 @@ private void FromDataToTextWriter<T>(TextWriter writer, IEnumerable<T> data) {
457
495
458
496
writer . Write ( Environment . NewLine ) ;
459
497
}
498
+
460
499
// Write data rows
461
500
foreach ( T ? obj in data ) {
462
501
string [ ] values = GetCsvValues ( obj ) . ToArray ( ) ;
@@ -481,6 +520,7 @@ private async Task FromDataToTextWriterAsync<T>(TextWriter writer, IEnumerable<T
481
520
await writer . WriteAsync ( Environment . NewLine ) ;
482
521
ct . ThrowIfCancellationRequested ( ) ;
483
522
}
523
+
484
524
// Write data rows
485
525
foreach ( T ? obj in data ) {
486
526
string [ ] values = GetCsvValues ( obj ) . ToArray ( ) ;
@@ -522,15 +562,15 @@ private async Task FromDictionaryToTextWriterAsync(TextWriter writer, IEnumerabl
522
562
IDictionary < string , string ? > firstDictionary = records . First ( ) ;
523
563
IEnumerable < string > headers = firstDictionary . Keys ;
524
564
await writer . WriteLineAsync ( string . Join ( config . ColumnSplit , headers ) ) ;
525
-
565
+
526
566
ct . ThrowIfCancellationRequested ( ) ;
527
567
}
528
568
529
569
// Write data rows
530
570
foreach ( IDictionary < string , string ? > dictionary in records ) {
531
571
IEnumerable < string > values = dictionary . Values . Select ( value => value ? . ToString ( ) ?? string . Empty ) ;
532
572
await writer . WriteLineAsync ( string . Join ( config . ColumnSplit , values ) ) ;
533
-
573
+
534
574
ct . ThrowIfCancellationRequested ( ) ;
535
575
}
536
576
}
@@ -539,43 +579,4 @@ private async Task FromDictionaryToTextWriterAsync(TextWriter writer, IEnumerabl
539
579
// -----------------------------------------------------------------------------------------------------------------
540
580
// Methods
541
581
// -----------------------------------------------------------------------------------------------------------------
542
- /// <inheritdoc />
543
- public void ClearCaches ( ) {
544
- PropertyCache . Clear ( ) ;
545
- HeaderCache . Clear ( ) ;
546
- }
547
-
548
- protected PropertyInfo [ ] GetCsvProperties < T > ( ) {
549
- Type type = typeof ( T ) ;
550
- if ( PropertyCache . TryGetValue ( type , out PropertyInfo [ ] ? propertyInfos ) ) return propertyInfos ;
551
- PropertyInfo [ ] propertyInfosArray = type . GetProperties ( ) . ToArray ( ) ;
552
- PropertyCache [ type ] = propertyInfosArray ;
553
- return propertyInfosArray ;
554
- }
555
-
556
- protected string [ ] GetCsvHeaders < T > ( ) {
557
- Type type = typeof ( T ) ;
558
- if ( HeaderCache . TryGetValue ( type , out string [ ] ? headers ) ) return headers ;
559
-
560
- string [ ] headersArray = GetCsvProperties < T > ( )
561
- . Select ( p => {
562
- if ( p . GetCustomAttribute < CsvColumnAttribute > ( ) is not { } attribute )
563
- return config . UseLowerCaseHeaders ? p . Name . ToLowerInvariant ( ) : p . Name ;
564
-
565
- return config . UseLowerCaseHeaders
566
- ? attribute . NameLowerInvariant
567
- : attribute . Name ;
568
- } )
569
- . ToArray ( ) ;
570
-
571
- HeaderCache [ type ] = headersArray ;
572
- return headersArray ;
573
- }
574
-
575
- protected IEnumerable < string > GetCsvValues < T > ( T ? obj ) {
576
- if ( obj is null ) return [ ] ;
577
-
578
- return GetCsvProperties < T > ( )
579
- . Select ( p => p . GetValue ( obj ) ? . ToString ( ) ?? string . Empty ) ;
580
- }
581
582
}
0 commit comments