@@ -16,12 +16,13 @@ namespace CodeOfChaos.Parsers.Csv;
1616/// Provides functionality to parse CSV files into various collection types.
1717/// </summary>
1818public class CsvParser ( CsvParserConfig config ) : ICsvParser {
19- protected readonly ConcurrentDictionary < Type , PropertyInfo [ ] > PropertyCache = new ( ) ;
2019 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+ }
2526 /// <summary>
2627 /// Creates an instance of <see cref="CsvParser" /> using the specified configuration action.
2728 /// </summary>
@@ -38,15 +39,50 @@ public static CsvParser FromConfig(Action<CsvParserConfig> configAction) {
3839 configAction ( config ) ;
3940 return new CsvParser ( config ) ;
4041 }
41-
4242 /// <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.
4444 /// </summary>
4545 /// <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.
4848 /// </returns>
4949 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+ // -----------------------------------------------------------------------------------------------------------------
5086
5187 // -----------------------------------------------------------------------------------------------------------------
5288 // Input Methods
@@ -173,6 +209,7 @@ public async ValueTask<List<T>> ToListAsync<T>(TextReader reader, CancellationTo
173209 await foreach ( Dictionary < string , string ? > item in FromTextReaderToDictionaryAsync ( reader , ct ) ) {
174210 results . Add ( item ) ;
175211 }
212+
176213 return results . ToArray ( ) ;
177214 }
178215
@@ -184,7 +221,7 @@ public async ValueTask<List<T>> ToListAsync<T>(TextReader reader, CancellationTo
184221 await foreach ( Dictionary < string , string ? > item in FromTextReaderToDictionaryAsync ( reader , ct ) ) {
185222 results . Add ( item ) ;
186223 }
187-
224+
188225 results . TrimExcess ( ) ;
189226 return results . ToArray ( ) ;
190227 }
@@ -205,7 +242,7 @@ public async ValueTask<List<T>> ToListAsync<T>(TextReader reader, CancellationTo
205242 await foreach ( Dictionary < string , string ? > item in FromTextReaderToDictionaryAsync ( reader , ct ) ) {
206243 results . Add ( item ) ;
207244 }
208-
245+
209246 results . TrimExcess ( ) ;
210247 return results ;
211248 }
@@ -217,7 +254,7 @@ public async ValueTask<List<T>> ToListAsync<T>(TextReader reader, CancellationTo
217254 await foreach ( Dictionary < string , string ? > item in FromTextReaderToDictionaryAsync ( reader , ct ) ) {
218255 results . Add ( item ) ;
219256 }
220-
257+
221258 results . TrimExcess ( ) ;
222259 return results ;
223260 }
@@ -355,14 +392,14 @@ private async IAsyncEnumerable<T> FromTextReaderAsync<T>(TextReader reader, [Enu
355392 }
356393
357394 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
359396 if ( line == null ) break ;
360397 }
361398 }
362399
363400 private void SetPropertyFromCsvColumn < T > ( T ? value , string [ ] headerColumns , string [ ] values ) where T : class , new ( ) {
364401 if ( value is null ) return ;
365-
402+
366403 foreach ( PropertyInfo prop in GetCsvProperties < T > ( ) ) {
367404 int columnIndex = Attribute . GetCustomAttribute ( prop , typeof ( CsvColumnAttribute ) ) is CsvColumnAttribute attribute
368405 ? Array . IndexOf ( headerColumns , attribute . Name )
@@ -401,6 +438,7 @@ private async IAsyncEnumerable<T> FromTextReaderAsync<T>(TextReader reader, [Enu
401438 string value = values [ j ] ;
402439 dict [ headerColumns [ j ] ] = value . IsNotNullOrEmpty ( ) ? value : null ;
403440 }
441+
404442 batch . Add ( dict ) ;
405443 }
406444
@@ -431,6 +469,7 @@ private async IAsyncEnumerable<T> FromTextReaderAsync<T>(TextReader reader, [Enu
431469 string value = values [ j ] ;
432470 dict [ headerColumns [ j ] ] = value . IsNotNullOrEmpty ( ) ? value : null ;
433471 }
472+
434473 batch . Add ( dict ) ;
435474 }
436475
@@ -439,12 +478,11 @@ private async IAsyncEnumerable<T> FromTextReaderAsync<T>(TextReader reader, [Enu
439478 }
440479
441480 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
443482 if ( line == null ) break ;
444483 }
445484 }
446485 #endregion
447-
448486 #region Generic Type Writer
449487 private void FromDataToTextWriter < T > ( TextWriter writer , IEnumerable < T > data ) {
450488 // Write header row
@@ -457,6 +495,7 @@ private void FromDataToTextWriter<T>(TextWriter writer, IEnumerable<T> data) {
457495
458496 writer . Write ( Environment . NewLine ) ;
459497 }
498+
460499 // Write data rows
461500 foreach ( T ? obj in data ) {
462501 string [ ] values = GetCsvValues ( obj ) . ToArray ( ) ;
@@ -481,6 +520,7 @@ private async Task FromDataToTextWriterAsync<T>(TextWriter writer, IEnumerable<T
481520 await writer . WriteAsync ( Environment . NewLine ) ;
482521 ct . ThrowIfCancellationRequested ( ) ;
483522 }
523+
484524 // Write data rows
485525 foreach ( T ? obj in data ) {
486526 string [ ] values = GetCsvValues ( obj ) . ToArray ( ) ;
@@ -522,15 +562,15 @@ private async Task FromDictionaryToTextWriterAsync(TextWriter writer, IEnumerabl
522562 IDictionary < string , string ? > firstDictionary = records . First ( ) ;
523563 IEnumerable < string > headers = firstDictionary . Keys ;
524564 await writer . WriteLineAsync ( string . Join ( config . ColumnSplit , headers ) ) ;
525-
565+
526566 ct . ThrowIfCancellationRequested ( ) ;
527567 }
528568
529569 // Write data rows
530570 foreach ( IDictionary < string , string ? > dictionary in records ) {
531571 IEnumerable < string > values = dictionary . Values . Select ( value => value ? . ToString ( ) ?? string . Empty ) ;
532572 await writer . WriteLineAsync ( string . Join ( config . ColumnSplit , values ) ) ;
533-
573+
534574 ct . ThrowIfCancellationRequested ( ) ;
535575 }
536576 }
@@ -539,43 +579,4 @@ private async Task FromDictionaryToTextWriterAsync(TextWriter writer, IEnumerabl
539579 // -----------------------------------------------------------------------------------------------------------------
540580 // Methods
541581 // -----------------------------------------------------------------------------------------------------------------
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- }
581582}
0 commit comments