Open chriswill opened 2 years ago
If one of the properties is an IEnumerable
such as a List<T>
or Array
, you'll get this exception.
When you query against Azure Cosmos db you get a List
None of the returned properties are actually IEnumerable, but it seems the CsvHelper lib gets confused by the JToken that is the value part of the returned JObject.
I'll likely end up writing the ITypeConverter since there are only two required methods and handling this should be easy given that I don't return any actual IEnumerables in the model, just the JTokens that inherit from them.
Where do you access the TypeConverterFactory to add in this ITypeConverter?
The TypeConverterCache mentioned in #1064 does not seem to be available in the instance of CsvConfiguration now and the example in #1838 requires a pre-defined class.
Just to help anyone who visits this post in the future, an example of the failing code is:
var records = new List<dynamic>();
JObject record = JObject.Parse("{\"department\":\"Engineering\",\"itemCount\":1}");
records.Add(record);
using (var writer = new StringWriter())
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteRecords(records);
var s = writer.ToString();
}
Until this is resolved in the main release, you have to create a JValueTypeConverter that implements ITypeConverter.
You can then register it with the TypeConverterCache like this:
using (var csv = new CsvWriter(writer, cultureInfo))
{
try
{
csv.Context.TypeConverterCache.AddConverter<JValue>(new JTypeTypeConverter());
await csv.WriteRecordsAsync(entities);
await csv.FlushAsync();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
Hi @chriswill , Thank you for raising this issue and the workaround you provided. I have been banging my head to find what was wrong with my code. The conversation above helps a lot.
I am new to using the CsvHelper library. Could you please provide some idea into how this JValueTypeConverter needs to be implemented. I would help me greatly. Thanks.
@Anu666
This is the implementation in my controller:
using (MemoryStream stream = new MemoryStream())
{
using (StreamWriter writer = new StreamWriter(stream))
{
using (var csv = new CsvWriter(writer, cultureInfo))
{
csv.Context.TypeConverterCache.AddConverter<JValue>(new JValueTypeConverter());
await csv.WriteRecordsAsync(entities);
await writer.FlushAsync();
stream.Seek(0, SeekOrigin.Begin);
bytes = stream.ToArray();
}
}
}
return File(bytes, "text/csv", fileName);
and the JValueTypeConverter class looks like this:
public class JValueTypeConverter : ITypeConverter
{
public object ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData)
{
throw new NotImplementedException();
}
public string ConvertToString(object? value, IWriterRow row, MemberMapData memberMapData)
{
if (value == null)
{
if (memberMapData.TypeConverterOptions.NullValues.Count > 0)
{
return memberMapData.TypeConverterOptions.NullValues.First();
}
return string.Empty;
}
if (value is IFormattable formattable)
{
string? format = memberMapData.TypeConverterOptions.Formats?.FirstOrDefault();
return formattable.ToString(format, memberMapData.TypeConverterOptions.CultureInfo);
}
#pragma warning disable CS8603 // Possible null reference return.
return value.ToString();
#pragma warning restore CS8603 // Possible null reference return.
}
}
The #pragma notations are to disable Resharper warnings.
I have utilized the DefaultTypeConverter class to bypass the issue for the time being. I see that your implementation of the ITypeConverter is similar to the DefaultTypeConverter class, but I believe your approach would be better to add any custom handling of the values in the future.
csv.Context.TypeConverterCache.AddConverter<JValue>(new DefaultTypeConverter());
Thank you for taking out some time to help me with this. @chriswill
I'm following the examples for writing a CSV using a list of dynamic by following the steps here. I'm executing my code in a net 6.0 Web API controller.
My model is extremely simple, as shown in the JSON representation below.
I don't know the schema of this model in advance.
Whenever my code gets to the csv.WriteRecords method it throws a CsvHelper.TypeConversion.TypeConverterException.
The only time I've found this error message in past issues is #455, but it doesn't seem related to my model.