Closed akriger closed 7 months ago
Couple questions. What do you mean by "I currently map them to the expected headers"? Also, It would help to know what exception you are running into. CsvHelper throws some exceptions for missing headers and the like, these exceptions can be disabled through the CsvHelperConfig in the EasyCsvConfiguration. Ex:
var easyCsvConfig = new EasyCsvConfiguration()
{
CsvHelperConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
{
MissingHeaderFound = null
}
}
However, you need the state of the IEasyCsv to match your class for the data to actually get in the right property. For example, if you have a property called “FirstName”, and you need to match it to “first”, “fir.” or whatever on the csv dynamically at runtime, you would have to do easyCsv.Mutate(x => x.ReplaceColumn(“first”, “FirstName”));
then get the the records.
I’m open to creating a function that would simplify this whole process, what signature would be easiest for you? Take a Dictionary<string, string>?
Also, I already have a Blazor component for when the headers are unknown. But that’s just a component and requires user input. I am considering factoring out the header matching code into an IEasyCsvMapper. All you would need to do is provide ExpectedHeaders for all your properties saying what headers names a csharp property should match to.
For some context the user selects a category from a dropdown list. Based on the category they select, this will change the possible headers that could be matched. These headers are pulled from another source via an API and at anytime could be modified in that other source. So I can't have a statically typed class to pass to T= in CsvTableHeaderMatcher, because the properties would change based on the category selected.
Once they choose a category, I'm calling the API to get the possible headers at that time and then adding them to the ExpectedHeader list.
private List<ExpectedHeader> _expectedHeaders = [];
// other code
private Task selectedFieldSet(string fieldSetId)
{
selectedName = fieldSetId;
var fieldSet = fieldSets?.FirstOrDefault(fs => fs.Name == selectedName);
_fieldTypeIds = fieldSet?.FieldTypeIds;
foreach (var fieldType in _fieldTypeIds)
{
_expectedHeaders.Add(new ExpectedHeader(fieldType));
}
return Task.CompletedTask;
}
My issue is what do I pass to the T= in the CsvTableHeaderMatcher component if I don't have a static class?
<CsvTableHeaderMatcher @ref="_tableHeaderMatcher" T="{what goes here}" Csv="_easyCsv" Frozen="_frozen" AllHeadersValidChanged="StateHasChanged" ExpectedHeaders="_expectedHeaders" AutoMatch="AutoMatching.Lenient"></CsvTableHeaderMatcher>
@akriger Take a look at #3 I'm thinking about moving the type parameter from the CsvTableHeaderMatcher
and moving it to GetRecords<T>
. The only other thing the type parameter is used for is autogenerating expected headers so I added a new parameter for that. Specifically, look at the new section I added to the read.me
Also, an important thing to note. Is that your current code needs change. It should be more like this, you have to create new lists.
private List<ExpectedHeader> _expectedHeaders;
private Task selectedFieldSet(string fieldSetId)
{
List<ExpectedHeaders> newExpectedHeaders = [];
selectedName = fieldSetId;
var fieldSet = fieldSets?.FirstOrDefault(fs => fs.Name == selectedName);
_fieldTypeIds = fieldSet?.FieldTypeIds;
foreach (var fieldType in _fieldTypeIds)
{
newExpectedHeaders.Add(new ExpectedHeader(fieldType));
}
_expectedHeaders = newExpectedHeaders.
return Task.CompletedTask;
}
Another important thing to note is that this constructor new ExpectedHeader(fieldType)
expects fieldType to be the CSharpPropertyName on the class you will be deserializing into, which I'm not sure if that's what you want.
@biegehydra I think removing the type parameter from CsvTableHeaderMatcher
and passing the type with GetRecords<T>
will fix the issue. Also, thanks for the catch on the _expectedHeaders
list.
@akriger I just published v1.0.4 of EasyCsv.Components. Let me know if that works for you and I will close this issue.
@biegehydra I updated and tested, but no records are returned if dynamic
is passed as the type. I tried the below after updating. _records
comes back null.
private List<dynamic>? _records;
// other code
private async Task GetRecords()
{
if (_tableHeaderMatcher == null) return;
_records = await _tableHeaderMatcher.GetRecords<dynamic>();
}
In the meantime, I was able to do the below, which works.
private List<dynamic>? _records;
// other code
private async Task GetRecords()
{
if (_tableHeaderMatcher == null) return;
_records = await _easyCsv?.GetRecordsAsync<dynamic>(EasyCsvConfig.CsvHelperConfig, csvContextProfile);
}
Can you show me the configurations you used that made it work?
@biegehydra here is what I have in the GetRecords() method.
private async Task GetRecords()
{
if (_tableHeaderMatcher == null) return;
var csvContextProfile = new CsvContextProfile();
if (ClassMaps != null)
{
csvContextProfile.ClassMaps = ClassMaps;
}
try
{
_easyCsv.RemoveUnusedHeaders();
_records = await _easyCsv?.GetRecordsAsync<dynamic>(EasyCsvConfig.CsvHelperConfig, csvContextProfile);
}
catch (Exception ex)
{
Logger?.LogError(ex, "Error getting records. CsvTableHeaderMatcher");
}
}
Okay, I'm going to close the issue.
What if I do not know how many headers or what the header names will be until runtime. My app gets the headers that will be used from an api and the header names or count of them could change at any time. I currently map them to the expected headers which works but calling GetRecords() fails. I’m putting them in a Dictionary currently.