How can I write headers with spaces using CsvHelper? - c#

I am using the CsvHelper library to generate a CSV file from an IEnumerable<Person>, where Person is a basic class.
public class Person
{
public string DisplayName { get; set; }
public int Age { get; set; }
}
I need to write quoted headers, i.e. instead of "DisplayName", the column in the resulting file should be "Display Name".
How can I do this?

Create a ClassMap.
void Main()
{
using (var stream = new MemoryStream())
using (var writer = new StreamWriter(stream))
using (var reader = new StreamReader(stream))
using (var csv = new CsvWriter(writer))
{
var records = new List<Person>
{
new Test { DisplayName = "one", Age = 1},
new Test { DisplayName = "two", Age = 2 },
};
csv.Configuration.RegisterClassMap<PersonMap>();
csv.WriteRecords(records);
writer.Flush();
stream.Position = 0;
Console.WriteLine(reader.ReadToEnd());
}
}
public class Person
{
public string DisplayName { get; set; }
public int Age { get; set; }
}
public sealed class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Map(m => m.DisplayName).Name("Display Name");
Map(m => m.Age);
}
}
Output:
Display Name,Age
one,1
two,2

I had no headers with workaround, so I did this quick workaround instead:
foreach (var property in typeof(MyCsvClass).GetProperties())
{
csvWriter.WriteField(property.Name.Replace('_', ' '));
}
csvWriter.NextRecord();
csvWriter.WriteRecords(models);
This takes the property names and replace underscore with space, so I could just name the properties with underscores instead of spaces, and it mapped correctly.
It requires, however, that you use the HasHeaderRecord = false option on the CsvWriter.

Related

Write CSV file with Upper Case Header

I am trying to write a csv file but with upper case headers.
Entries is a collection of objects.
using (var streamWriter = new StreamWriter(csvStream))
{
var csvConfig = new CsvConfiguration(CultureInfo.CurrentCulture)
{
PrepareHeaderForMatch = args => args.Header.ToUpper()
};
using (var csvWriter = new CsvWriter(streamWriter, csvConfig))
{
csvWriter.WriteRecords<T>(entries);
streamWriter.Flush();
}
}
I can see the file has been written but headers are not in capital letter. What is wrong here?
PrepareHeaderForMatch is for matching and mapping headers to your model when you want to Read a csv file not writing. for writing to csv file, you need to set Name attribute for your properties like this:
public class CsvModel
{
[Name("NAME")]
public string Name { get; set; }
[Name("FAMILY")]
public string Family { get; set; }
}
Or you can set header names in csv mapping:
public class CsvModelMap : ClassMap<CsvModel>
{
public CsvModelMap()
{
Map(m => m.Name).Index(0).Name("NAME");
Map(m => m.Family).Index(1).Name("FAMILY");
}
}
If you want a more generic approach to making all the headers uppercase, you can use the Map(Type classType, MemberInfo member, bool useExistingMap = true) overload of the Map method.
void Main()
{
var records = new List<CsvModel>
{
new CsvModel { Name = "name", Family = "first" },
};
using (var csv = new CsvWriter(Console.Out, CultureInfo.InvariantCulture))
{
var fooMap = new DefaultClassMap<CsvModel>();
var properties = typeof(CsvModel).GetProperties();
foreach (var property in properties)
{
fooMap.Map(typeof(CsvModel), property).Name(property.Name.ToUpper());
}
csv.Context.RegisterClassMap(fooMap);
csv.WriteRecords(records);
}
}
public class CsvModel
{
public string Name { get; set; }
public string Family { get; set; }
}

CSV Helper Wraps string field ' Name' into double quotes after delimiter?

I have some troubles with CSV result in my file,
I have written the next configuration in code ( I am working with CSVHelper library)
public class ReportModelMap : ClassMap<ReportModel>
{
public ReportModelMap()
{
Map(x => x.Name).Index(9).Name(" Name");
}
}
The customer requires to add the space between the 'Name' text => " Name".
However, the library wraps the string into ' Name' into double quotes, and for me, it's wrong behavior.
How can I make --Surname; Name-- instead of --Surname;" Name"--?
I can't find any specific configuration to fix it in CsvWriter
My saving logic if it needed
using (var writer = new StreamWriter(path))
using (var csvWriter = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csvWriter.Configuration.RegisterClassMap<ReportModelMap>();
csvWriter.Configuration.Delimiter = ";";
csvWriter.WriteRecords(ratingModels);
}
#Panagiotis Kanavos is correct. You can use ShouldQuote to override the quoting behavior for just that one heading.
void Main()
{
var ratingModels = new List<ReportModel>
{
new ReportModel { Id = 1, Surname = "Surname", Name = " Name" }
};
//using (var writer = new StreamWriter(path))
using (var csvWriter = new CsvWriter(Console.Out, CultureInfo.InvariantCulture))
{
csvWriter.Configuration.RegisterClassMap<ReportModelMap>();
csvWriter.Configuration.Delimiter = ";";
csvWriter.Configuration.ShouldQuote = (field, context) =>
{
if (!context.HasHeaderBeenWritten && field == " Name")
{
return false;
}
else
{
return ConfigurationFunctions.ShouldQuote(field, context);
}
};
csvWriter.WriteRecords(ratingModels);
}
}
// You can define other methods, fields, classes and namespaces here
public class ReportModelMap : ClassMap<ReportModel>
{
public ReportModelMap()
{
Map(x => x.Id).Index(0);
Map(x => x.Surname).Index(1);
Map(x => x.Name).Index(2).Name(" Name");
}
}
public class ReportModel
{
public int Id { get; set; }
public string Surname { get; set; }
public string Name { get; set; }
}

Setting custom column names in a class properties to export to a file

I'm trying to create a .dat file with a pipe (|) separator.
I'm not sure how I can set custom column names I can use in a .dat file.
I have a class that would look like this :
public partial class ExportData
{
[DatColumnName="Test Code 123"]
public string Code { get; set; }
[DatColumnName="Test Name 123"]
public string Name { get; set; }
[DatColumnName="Test Address_123"]
public string Address { get; set; }
}
As a result, I would like to get something like this in the file:
Test Code 123|Test Name 123|Test Address_123
1|Name1|Address1
etc.
I guess below code will works for you.
I could handle it without custom attribute.
Code
public static class Extensions
{
public static string Piped<T>(this IList<T> source)
{
var sb = new StringBuilder();
var pInfo = typeof(T).GetProperties().Where(x => x.GetCustomAttributes<DisplayNameAttribute>().Any());
bool first = true;
foreach (var i in source)
{
if (first)
{
//foreach (var h in pInfo.Select(x => x.GetCustomAttribute<DisplayNameAttribute>().DisplayName))
//{
// sb.Append(h);
// sb.Append('|');
//}
sb.Append(string.Join('|', pInfo.Select(x => x.GetCustomAttribute<DisplayNameAttribute>().DisplayName)));
sb.Append(Environment.NewLine);
first = false;
}
foreach (var y in i.GetType().GetProperties().Where(x => x.GetCustomAttributes<DisplayNameAttribute>().Any()))
{
sb.Append(i.GetType().GetProperty(y.Name).GetValue(i, null).ToString());
sb.Append('|');
}
sb.Append(Environment.NewLine);
}
return sb.ToString();
}
}
Usage
public partial class ExportData
{
[DisplayName("Test Code 123")]
public string Code { get; set; }
[DisplayName("Test Name 123")]
public string Name { get; set; }
[DisplayName("whatever you want")]
public string Address { get; set; }
}
static void Main(string[] args)
{
var lst = new List<ExportData>() {
new ExportData{ Code = "c1", Name ="n1", Address = "a1" },
new ExportData{ Code = "c2", Name ="n2", Address = "a2" },
new ExportData{ Code = "c3", Name ="n3", Address = "a3" },
};
Console.WriteLine(lst.Piped());
Console.ReadKey();
}
Result
Test Code 123|Test Name 123|Whatever you want|
c1|n1|a1|
c2|n2|a2|
c3|n3|a3|

Read values from CSV when columns names are vertical

I have an implementation of CSV helper which currently reads CSV's in the traditional format:
Name, Address, Age
"Foo", "Foo's address", 24
"Bar", "Bar's address", 19
I use a class map to map the fields by name to my Person object in the following way:
using (var reader = new StreamReader(file, Encoding.UTF8))
{
using (var csvReader = new CsvReader(reader))
{
csvReader.Configuration.RegisterClassMap<ContentMapper>();
var records = csvReader.GetRecords<Person>().ToArray();
}
}
I need an implementation which reads the exact same data but in a vertical format which originates from data where the column names are vertical in the first column and the data follows in columns instead of rows.
Name, "Foo", "Bar"
Address, "Foo's address", "Bar's address"
Age, 24, 19
What would be the best way to handle a CSV in this format whilst retaining the original mapping?
Try this one
public class Person
{
public string Name { get; set; }
public string Address { get; set; }
public int Age { get; set; }
public List<Person> ReadFile(string path)
{
char[] charsToTrim1 = {'\\', ' ', '"', '\"'};
var fileData = File.ReadAllLines(path);
var outputData = new List<Person>();
for (var i = 0; i < fileData.Length; i++)
{
var tmpData = fileData[i].Split(',');
for (var j = 0; j < tmpData.Length; j++)
{
var t1 = tmpData[j].Trim(charsToTrim1);
if (j == 0)
continue;
switch (i)
{
case 0:
{
var tmPerson = new Person {Name = t1};
outputData.Add(tmPerson);
}
break;
case 1:
{
outputData[j - 1].Address = t1;
}
break;
case 2:
{
outputData[j - 1].Age = Convert.ToInt32(t1);
}
break;
}
}
}
return outputData;
}
}
This could probably be cleaned up a bit, but it does seem to work.
Use CsvHelper to pull in the records as List<dynamic>
Rotate the records into a new List<dynamic> so the first field in each row becomes the property name of the dynamic record.
Use CsvHelper to write the new list to memory.
Use CsvHelper to read the records back in using your ClassMap
public class Program
{
public static void Main(string[] args)
{
var flippedRecords = new List<dynamic>();
using (MemoryStream stream = new MemoryStream())
using (StreamWriter writer = new StreamWriter(stream))
using (StreamReader reader = new StreamReader(stream))
using (CsvReader csv = new CsvReader(reader))
{
writer.WriteLine("Name,Foo,Bar");
writer.WriteLine("Address,Foo's address,\"Bar's address with, comma\"");
writer.WriteLine("Age,24,19");
writer.Flush();
stream.Position = 0;
csv.Configuration.HasHeaderRecord = false;
// Get the records from the CSV file.
var records = csv.GetRecords<dynamic>().ToList();
// Rotate the records into a new dynamic list.
var rows = new List<IDictionary<string, object>>();
foreach (var row in records)
{
rows.Add(row as IDictionary<string, object>);
}
for (int i = 2; i <= rows[0].Count; i++)
{
var flippedRecord = new ExpandoObject() as IDictionary<string, object>;
foreach (var row in rows)
{
flippedRecord.Add((string)row["Field1"], row["Field" + i]);
}
flippedRecords.Add(flippedRecord);
}
}
using (MemoryStream stream = new MemoryStream())
using (StreamWriter writer = new StreamWriter(stream))
using (CsvWriter csvWriter = new CsvWriter(writer))
using (StreamReader reader = new StreamReader(stream))
using (CsvReader csvReader = new CsvReader(reader))
{
// Write the new list to memory
csvWriter.WriteRecords(flippedRecords);
writer.Flush();
stream.Position = 0;
// Read in the person records using a ClassMap.
csvReader.Configuration.RegisterClassMap<PersonMap>();
var people = csvReader.GetRecords<Person>().ToArray();
}
}
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public int Age { get; set; }
}
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Map(m => m.FirstName).Name("Name");
Map(m => m.Address);
Map(m => m.Age);
}
}

CsvHelper : Adding a title using CsvHelper

I am using CsvHelper to convert dapper objects to CsvFiles. I am using classmaps to map properties for indices and name mapping. The issue is I need a row with the table title before the records are written as mentioned below:
My old code without the title:
using (var writer = new StreamWriter(#"C:\Users\NPandian\Desktop\test.csv", false, System.Text.Encoding.UTF8))
using (var csvWriter = new CsvWriter(writer))
{
var ReportName = "Test Class";
csvWriter.Configuration.RegisterClassMap(classMap);
csvWriter.WriteRecords(records);
writer.Close();
}
Old Csv:
My Current work around code:
using (var writer = new StringWriter())
using (var csvWriter = new CsvWriter(writer))
{
var ReportName = "Test Class";
csvWriter.Configuration.RegisterClassMap(classMap);
csvWriter.WriteRecords(records);
writer.Close();
return $"ReportName:, {ReportName},\n{csvWriter.Context.Writer.ToString()}";
}
My Questions:
1) Is it possible with CsvHelper?
2) If so How?
You can write fields and rows by hand.
void Main()
{
var records = new List<Foo>
{
new Foo { Id = 1, Name = "one" },
new Foo { Id = 2, Name = "two" },
};
using (var writer = new StringWriter())
using (var csv = new CsvWriter(writer))
{
csv.Configuration.RegisterClassMap<FooMap>();
csv.WriteField("Title:");
csv.WriteField("Title");
csv.NextRecord();
csv.WriteRecords(records);
writer.ToString().Dump();
}
}
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
public class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id).Index(0).Name("S.No.");
Map(m => m.Name).Index(1);
}
}
Output:
Title:,Title
S.No.,Name
1,one
2,two
CSV files have no titles. The question describes a flat text report, not a CSV file. CsvHelper, as the name implies, is a helper library that writes CSVs, it's not a full featured report generator.
The library allows other code to write arbitrary text before or after it finished though, as it works on top of an abstract TextWriter instead of a stream or file. One could even write extra text between records if required.
You can use the writer you already have to write whatever you want before or after the call to csvWriter.WriteRecords(records);, eg :
using (var writer = new StreamWriter(#"C:\Users\NPandian\Desktop\test.csv", false, System.Text.Encoding.UTF8))
using (var csvWriter = new CsvWriter(writer))
{
var ReportName = "Test Class";
csvWriter.Configuration.RegisterClassMap(classMap);
//Doesn't matter where this is called as long as it is before `WriteRecords`
writer.WriteLine($"ReportName:, {ReportName}");
csvWriter.WriteRecords(records);
//No need to explicitly close, that's what `using` is for
}
CsvWriter accepts any TextWriter object and just writes its data to that writer. It doesn't try to modify it in any other way. It won't affect any other text already written to that TextWriter
For CSVHelper version 15.0.0 and above use:
void Main()
{
var records = new List<Foo>
{
new Foo { Id = 1, Name = "one" },
new Foo { Id = 2, Name = "two" },
};
using (var writer = new StringWriter())
using (var csv = new CsvWriter(writer, CultureInfo.CurrentCulture))
{
csv.Configuration.RegisterClassMap<FooMap>();
csv.WriteField("Title:");
csv.WriteField("Title");
csv.NextRecord();
csv.WriteRecords(records);
writer.ToString().Dump();
}
}
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
public class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id).Index(0).Name("S.No.");
Map(m => m.Name).Index(1);
}
}

Categories